mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Add initial implementation of flutter assemble (#32816)
This commit is contained in:
parent
fb9ff9296e
commit
e91b98a41f
|
@ -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;
|
||||
|
|
686
packages/flutter_tools/lib/src/build_system/build_system.dart
Normal file
686
packages/flutter_tools/lib/src/build_system/build_system.dart
Normal file
|
@ -0,0 +1,686 @@
|
|||
// 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 'package:async/async.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:pool/pool.dart';
|
||||
|
||||
import '../base/file_system.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../cache.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'file_hash_store.dart';
|
||||
import 'source.dart';
|
||||
import 'targets/assets.dart';
|
||||
import 'targets/dart.dart';
|
||||
import 'targets/ios.dart';
|
||||
import 'targets/linux.dart';
|
||||
import 'targets/macos.dart';
|
||||
import 'targets/windows.dart';
|
||||
|
||||
export 'source.dart';
|
||||
|
||||
/// The function signature of a build target which can be invoked to perform
|
||||
/// the underlying task.
|
||||
typedef BuildAction = FutureOr<void> Function(
|
||||
Map<String, ChangeType> inputs, Environment environment);
|
||||
|
||||
/// A description of the update to each input file.
|
||||
enum ChangeType {
|
||||
/// The file was added.
|
||||
Added,
|
||||
/// The file was deleted.
|
||||
Removed,
|
||||
/// The file was modified.
|
||||
Modified,
|
||||
}
|
||||
|
||||
/// Configuration for the build system itself.
|
||||
class BuildSystemConfig {
|
||||
/// Create a new [BuildSystemConfig].
|
||||
const BuildSystemConfig({this.resourcePoolSize});
|
||||
|
||||
/// The maximum number of concurrent tasks the build system will run.
|
||||
///
|
||||
/// If not provided, defaults to [platform.numberOfProcessors].
|
||||
final int resourcePoolSize;
|
||||
}
|
||||
|
||||
/// A Target describes a single step during a flutter build.
|
||||
///
|
||||
/// The target inputs are required to be files discoverable via a combination
|
||||
/// of at least one of the environment values and zero or more local values.
|
||||
///
|
||||
/// To determine if the action for a target needs to be executed, the
|
||||
/// [BuildSystem] performs a hash of the file contents for both inputs and
|
||||
/// outputs. This is tracked separately in the [FileHashStore].
|
||||
///
|
||||
/// A Target has both implicit and explicit inputs and outputs. Only the
|
||||
/// later are safe to evaluate before invoking the [buildAction]. For example,
|
||||
/// a wildcard output pattern requires the outputs to exist before it can
|
||||
/// glob files correctly.
|
||||
///
|
||||
/// - All listed inputs are considered explicit inputs.
|
||||
/// - Outputs which are provided as [Source.pattern].
|
||||
/// without wildcards are considered explicit.
|
||||
/// - The remaining outputs are considered implicit.
|
||||
///
|
||||
/// For each target, executing its action creates a corresponding stamp file
|
||||
/// which records both the input and output files. This file is read by
|
||||
/// subsequent builds to determine which file hashes need to be checked. If the
|
||||
/// stamp file is missing, the target's action is always rerun.
|
||||
///
|
||||
/// file: `example_target.stamp`
|
||||
///
|
||||
/// {
|
||||
/// "inputs": [
|
||||
/// "absolute/path/foo",
|
||||
/// "absolute/path/bar",
|
||||
/// ...
|
||||
/// ],
|
||||
/// "outputs": [
|
||||
/// "absolute/path/fizz"
|
||||
/// ]
|
||||
/// }
|
||||
///
|
||||
/// ## Code review
|
||||
///
|
||||
/// ### Targes should only depend on files that are provided as inputs
|
||||
///
|
||||
/// Example: gen_snapshot must be provided as an input to the aot_elf
|
||||
/// build steps, even though it isn't a source file. This ensures that changes
|
||||
/// to the gen_snapshot binary (during a local engine build) correctly
|
||||
/// trigger a corresponding build update.
|
||||
///
|
||||
/// Example: aot_elf has a dependency on the dill and packages file
|
||||
/// produced by the kernel_snapshot step.
|
||||
///
|
||||
/// ### Targest should declare all outputs produced
|
||||
///
|
||||
/// If a target produces an output it should be listed, even if it is not
|
||||
/// intended to be consumed by another target.
|
||||
///
|
||||
/// ## Unit testing
|
||||
///
|
||||
/// Most targets will invoke an external binary which makes unit testing
|
||||
/// trickier. It is recommend that for unit testing that a Fake is used and
|
||||
/// provided via the dependency injection system. a [Testbed] may be used to
|
||||
/// set up the environment before the test is run. Unit tests should fully
|
||||
/// exercise the rule, ensuring that the existing input and output verification
|
||||
/// logic can run, as well as verifying it correctly handles provided defines
|
||||
/// and meets any additional contracts present in the target.
|
||||
class Target {
|
||||
const Target({
|
||||
@required this.name,
|
||||
@required this.inputs,
|
||||
@required this.outputs,
|
||||
@required this.buildAction,
|
||||
this.dependencies = const <Target>[],
|
||||
});
|
||||
|
||||
/// The user-readable name of the target.
|
||||
///
|
||||
/// This information is surfaced in the assemble commands and used as an
|
||||
/// argument to build a particular target.
|
||||
final String name;
|
||||
|
||||
/// The dependencies of this target.
|
||||
final List<Target> dependencies;
|
||||
|
||||
/// The input [Source]s which are diffed to determine if a target should run.
|
||||
final List<Source> inputs;
|
||||
|
||||
/// The output [Source]s which we attempt to verify are correctly produced.
|
||||
final List<Source> outputs;
|
||||
|
||||
/// The action which performs this build step.
|
||||
final BuildAction buildAction;
|
||||
|
||||
/// Collect hashes for all inputs to determine if any have changed.
|
||||
Future<Map<String, ChangeType>> computeChanges(
|
||||
List<File> inputs,
|
||||
Environment environment,
|
||||
FileHashStore fileHashStore,
|
||||
) async {
|
||||
final Map<String, ChangeType> updates = <String, ChangeType>{};
|
||||
final File stamp = _findStampFile(environment);
|
||||
final Set<String> previousInputs = <String>{};
|
||||
final List<String> previousOutputs = <String>[];
|
||||
|
||||
// If the stamp file doesn't exist, we haven't run this step before and
|
||||
// all inputs were added.
|
||||
if (stamp.existsSync()) {
|
||||
final String content = stamp.readAsStringSync();
|
||||
// Something went wrong writing the stamp file.
|
||||
if (content == null || content.isEmpty) {
|
||||
stamp.deleteSync();
|
||||
} else {
|
||||
final Map<String, Object> values = json.decode(content);
|
||||
final List<Object> inputs = values['inputs'];
|
||||
final List<Object> outputs = values['outputs'];
|
||||
inputs.cast<String>().forEach(previousInputs.add);
|
||||
outputs.cast<String>().forEach(previousOutputs.add);
|
||||
}
|
||||
}
|
||||
|
||||
// For each input type, first determine if we've already computed the hash
|
||||
// for it. If not and it is a directory we skip hashing and instead use a
|
||||
// timestamp. If it is a file we collect it to be sent off for hashing as
|
||||
// a group.
|
||||
final List<File> sourcesToHash = <File>[];
|
||||
final List<File> missingInputs = <File>[];
|
||||
for (File file in inputs) {
|
||||
if (!file.existsSync()) {
|
||||
missingInputs.add(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
final String absolutePath = file.resolveSymbolicLinksSync();
|
||||
final String previousHash = fileHashStore.previousHashes[absolutePath];
|
||||
if (fileHashStore.currentHashes.containsKey(absolutePath)) {
|
||||
final String currentHash = fileHashStore.currentHashes[absolutePath];
|
||||
if (currentHash != previousHash) {
|
||||
updates[absolutePath] = previousInputs.contains(absolutePath)
|
||||
? ChangeType.Modified
|
||||
: ChangeType.Added;
|
||||
}
|
||||
} else {
|
||||
sourcesToHash.add(file);
|
||||
}
|
||||
}
|
||||
// Check if any outputs were deleted or modified from the previous run.
|
||||
for (String previousOutput in previousOutputs) {
|
||||
final File file = fs.file(previousOutput);
|
||||
if (!file.existsSync()) {
|
||||
updates[previousOutput] = ChangeType.Removed;
|
||||
continue;
|
||||
}
|
||||
final String absolutePath = file.resolveSymbolicLinksSync();
|
||||
final String previousHash = fileHashStore.previousHashes[absolutePath];
|
||||
if (fileHashStore.currentHashes.containsKey(absolutePath)) {
|
||||
final String currentHash = fileHashStore.currentHashes[absolutePath];
|
||||
if (currentHash != previousHash) {
|
||||
updates[absolutePath] = previousInputs.contains(absolutePath)
|
||||
? ChangeType.Modified
|
||||
: ChangeType.Added;
|
||||
}
|
||||
} else {
|
||||
sourcesToHash.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingInputs.isNotEmpty) {
|
||||
throw MissingInputException(missingInputs, name);
|
||||
}
|
||||
|
||||
// If we have files to hash, compute them asynchronously and then
|
||||
// update the result.
|
||||
if (sourcesToHash.isNotEmpty) {
|
||||
final List<File> dirty = await fileHashStore.hashFiles(sourcesToHash);
|
||||
for (File file in dirty) {
|
||||
final String absolutePath = file.resolveSymbolicLinksSync();
|
||||
updates[absolutePath] = previousInputs.contains(absolutePath)
|
||||
? ChangeType.Modified
|
||||
: ChangeType.Added;
|
||||
}
|
||||
}
|
||||
|
||||
// Find which, if any, inputs have been deleted.
|
||||
final Set<String> currentInputPaths = Set<String>.from(
|
||||
inputs.map<String>((File entity) => entity.resolveSymbolicLinksSync())
|
||||
);
|
||||
for (String previousInput in previousInputs) {
|
||||
if (!currentInputPaths.contains(previousInput)) {
|
||||
updates[previousInput] = ChangeType.Removed;
|
||||
}
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
/// Invoke to remove the stamp file if the [buildAction] threw an exception;
|
||||
void clearStamp(Environment environment) {
|
||||
final File stamp = _findStampFile(environment);
|
||||
if (stamp.existsSync()) {
|
||||
stamp.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
void _writeStamp(
|
||||
List<File> inputs,
|
||||
List<File> outputs,
|
||||
Environment environment,
|
||||
) {
|
||||
final File stamp = _findStampFile(environment);
|
||||
final List<String> inputPaths = <String>[];
|
||||
for (File input in inputs) {
|
||||
inputPaths.add(input.resolveSymbolicLinksSync());
|
||||
}
|
||||
final List<String> outputPaths = <String>[];
|
||||
for (File output in outputs) {
|
||||
outputPaths.add(output.resolveSymbolicLinksSync());
|
||||
}
|
||||
final Map<String, Object> result = <String, Object>{
|
||||
'inputs': inputPaths,
|
||||
'outputs': outputPaths,
|
||||
};
|
||||
if (!stamp.existsSync()) {
|
||||
stamp.createSync();
|
||||
}
|
||||
stamp.writeAsStringSync(json.encode(result));
|
||||
}
|
||||
|
||||
/// Resolve the set of input patterns and functions into a concrete list of
|
||||
/// files.
|
||||
List<File> resolveInputs(
|
||||
Environment environment,
|
||||
) {
|
||||
return _resolveConfiguration(inputs, environment, implicit: true, inputs: true);
|
||||
}
|
||||
|
||||
/// Find the current set of declared outputs, including wildcard directories.
|
||||
///
|
||||
/// The [implicit] flag controls whether it is safe to evaluate [Source]s
|
||||
/// which uses functions, behaviors, or patterns.
|
||||
List<File> resolveOutputs(
|
||||
Environment environment,
|
||||
{ bool implicit = true, }
|
||||
) {
|
||||
final List<File> outputEntities = _resolveConfiguration(outputs, environment, implicit: implicit, inputs: false);
|
||||
if (implicit) {
|
||||
verifyOutputDirectories(outputEntities, environment, this);
|
||||
}
|
||||
return outputEntities;
|
||||
}
|
||||
|
||||
/// Performs a fold across this target and its dependencies.
|
||||
T fold<T>(T initialValue, T combine(T previousValue, Target target)) {
|
||||
final T dependencyResult = dependencies.fold(
|
||||
initialValue, (T prev, Target t) => t.fold(prev, combine));
|
||||
return combine(dependencyResult, this);
|
||||
}
|
||||
|
||||
/// Convert the target to a JSON structure appropriate for consumption by
|
||||
/// external systems.
|
||||
///
|
||||
/// This requires constants from the [Environment] to resolve the paths of
|
||||
/// inputs and the output stamp.
|
||||
Map<String, Object> toJson(Environment environment) {
|
||||
return <String, Object>{
|
||||
'name': name,
|
||||
'dependencies': dependencies.map((Target target) => target.name).toList(),
|
||||
'inputs': resolveInputs(environment)
|
||||
.map((File file) => file.resolveSymbolicLinksSync())
|
||||
.toList(),
|
||||
'outputs': resolveOutputs(environment, implicit: false)
|
||||
.map((File file) => file.path)
|
||||
.toList(),
|
||||
'stamp': _findStampFile(environment).absolute.path,
|
||||
};
|
||||
}
|
||||
|
||||
/// Locate the stamp file for a particular target name and environment.
|
||||
File _findStampFile(Environment environment) {
|
||||
final String fileName = '$name.stamp';
|
||||
return environment.buildDir.childFile(fileName);
|
||||
}
|
||||
|
||||
static List<File> _resolveConfiguration(
|
||||
List<Source> config, Environment environment, { bool implicit = true, bool inputs = true }) {
|
||||
final SourceVisitor collector = SourceVisitor(environment, inputs);
|
||||
for (Source source in config) {
|
||||
source.accept(collector);
|
||||
}
|
||||
return collector.sources;
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Environment] defines several constants for use during the build.
|
||||
///
|
||||
/// The environment contains configuration and file paths that are safe to
|
||||
/// depend on and reference during the build.
|
||||
///
|
||||
/// Example (Good):
|
||||
///
|
||||
/// Use the environment to determine where to write an output file.
|
||||
///
|
||||
/// environment.buildDir.childFile('output')
|
||||
/// ..createSync()
|
||||
/// ..writeAsStringSync('output data');
|
||||
///
|
||||
/// Example (Bad):
|
||||
///
|
||||
/// Use a hard-coded path or directory relative to the current working
|
||||
/// directory to write an output file.
|
||||
///
|
||||
/// fs.file('build/linux/out')
|
||||
/// ..createSync()
|
||||
/// ..writeAsStringSync('output data');
|
||||
///
|
||||
/// Example (Good):
|
||||
///
|
||||
/// Using the build mode to produce different output. Note that the action
|
||||
/// is still responsible for outputting a different file, as defined by the
|
||||
/// corresponding output [Source].
|
||||
///
|
||||
/// final BuildMode buildMode = getBuildModeFromDefines(environment.defines);
|
||||
/// if (buildMode == BuildMode.debug) {
|
||||
/// environment.buildDir.childFile('debug.output')
|
||||
/// ..createSync()
|
||||
/// ..writeAsStringSync('debug');
|
||||
/// } else {
|
||||
/// environment.buildDir.childFile('non_debug.output')
|
||||
/// ..createSync()
|
||||
/// ..writeAsStringSync('non_debug');
|
||||
/// }
|
||||
class Environment {
|
||||
/// Create a new [Environment] object.
|
||||
///
|
||||
/// Only [projectDir] is required. The remaining environment locations have
|
||||
/// defaults based on it.
|
||||
factory Environment({
|
||||
@required Directory projectDir,
|
||||
Directory buildDir,
|
||||
Map<String, String> defines = const <String, String>{},
|
||||
}) {
|
||||
// Compute a unique hash of this build's particular environment.
|
||||
// Sort the keys by key so that the result is stable. We always
|
||||
// include the engine and dart versions.
|
||||
String buildPrefix;
|
||||
final List<String> keys = defines.keys.toList()..sort();
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
for (String key in keys) {
|
||||
buffer.write(key);
|
||||
buffer.write(defines[key]);
|
||||
}
|
||||
// in case there was no configuration, provide some value.
|
||||
buffer.write('Flutter is awesome');
|
||||
final String output = buffer.toString();
|
||||
final Digest digest = md5.convert(utf8.encode(output));
|
||||
buildPrefix = hex.encode(digest.bytes);
|
||||
|
||||
final Directory rootBuildDir = buildDir ?? projectDir.childDirectory('build');
|
||||
final Directory buildDirectory = rootBuildDir.childDirectory(buildPrefix);
|
||||
return Environment._(
|
||||
projectDir: projectDir,
|
||||
buildDir: buildDirectory,
|
||||
rootBuildDir: rootBuildDir,
|
||||
cacheDir: Cache.instance.getRoot(),
|
||||
defines: defines,
|
||||
);
|
||||
}
|
||||
|
||||
Environment._({
|
||||
@required this.projectDir,
|
||||
@required this.buildDir,
|
||||
@required this.rootBuildDir,
|
||||
@required this.cacheDir,
|
||||
@required this.defines,
|
||||
});
|
||||
|
||||
/// The [Source] value which is substituted with the path to [projectDir].
|
||||
static const String kProjectDirectory = '{PROJECT_DIR}';
|
||||
|
||||
/// The [Source] value which is substituted with the path to [buildDir].
|
||||
static const String kBuildDirectory = '{BUILD_DIR}';
|
||||
|
||||
/// The [Source] value which is substituted with the path to [cacheDir].
|
||||
static const String kCacheDirectory = '{CACHE_DIR}';
|
||||
|
||||
/// The [Source] value which is substituted with a path to the flutter root.
|
||||
static const String kFlutterRootDirectory = '{FLUTTER_ROOT}';
|
||||
|
||||
/// The `PROJECT_DIR` environment variable.
|
||||
///
|
||||
/// This should be root of the flutter project where a pubspec and dart files
|
||||
/// can be located.
|
||||
final Directory projectDir;
|
||||
|
||||
/// The `BUILD_DIR` environment variable.
|
||||
///
|
||||
/// Defaults to `{PROJECT_ROOT}/build`. The root of the output directory where
|
||||
/// build step intermediates and outputs are written.
|
||||
final Directory buildDir;
|
||||
|
||||
/// The `CACHE_DIR` environment variable.
|
||||
///
|
||||
/// Defaults to `{FLUTTER_ROOT}/bin/cache`. The root of the artifact cache for
|
||||
/// the flutter tool.
|
||||
final Directory cacheDir;
|
||||
|
||||
/// Additional configuration passed to the build targets.
|
||||
///
|
||||
/// Setting values here forces a unique build directory to be chosen
|
||||
/// which prevents the config from leaking into different builds.
|
||||
final Map<String, String> defines;
|
||||
|
||||
/// The root build directory shared by all builds.
|
||||
final Directory rootBuildDir;
|
||||
}
|
||||
|
||||
/// The result information from the build system.
|
||||
class BuildResult {
|
||||
BuildResult(this.success, this.exceptions, this.performance);
|
||||
|
||||
final bool success;
|
||||
final Map<String, ExceptionMeasurement> exceptions;
|
||||
final Map<String, PerformanceMeasurement> performance;
|
||||
|
||||
bool get hasException => exceptions.isNotEmpty;
|
||||
}
|
||||
|
||||
/// The build system is responsible for invoking and ordering [Target]s.
|
||||
class BuildSystem {
|
||||
BuildSystem([Map<String, Target> targets])
|
||||
: targets = targets ?? _defaultTargets;
|
||||
|
||||
/// All currently registered targets.
|
||||
static final Map<String, Target> _defaultTargets = <String, Target>{
|
||||
unpackMacos.name: unpackMacos,
|
||||
macosApplication.name: macosApplication,
|
||||
macoReleaseApplication.name: macoReleaseApplication,
|
||||
unpackLinux.name: unpackLinux,
|
||||
unpackWindows.name: unpackWindows,
|
||||
copyAssets.name: copyAssets,
|
||||
kernelSnapshot.name: kernelSnapshot,
|
||||
aotElfProfile.name: aotElfProfile,
|
||||
aotElfRelease.name: aotElfRelease,
|
||||
aotAssemblyProfile.name: aotAssemblyProfile,
|
||||
aotAssemblyRelease.name: aotAssemblyRelease,
|
||||
releaseIosApplication.name: releaseIosApplication,
|
||||
profileIosApplication.name: profileIosApplication,
|
||||
debugIosApplication.name: debugIosApplication,
|
||||
};
|
||||
|
||||
final Map<String, Target> targets;
|
||||
|
||||
/// Build the target `name` and all of its dependencies.
|
||||
Future<BuildResult> build(
|
||||
String name,
|
||||
Environment environment,
|
||||
BuildSystemConfig buildSystemConfig,
|
||||
) async {
|
||||
final Target target = _getNamedTarget(name);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
|
||||
// Load file hash store from previous builds.
|
||||
final FileHashStore fileCache = FileHashStore(environment)
|
||||
..initialize();
|
||||
|
||||
// Perform sanity checks on build.
|
||||
checkCycles(target);
|
||||
|
||||
final _BuildInstance buildInstance = _BuildInstance(environment, fileCache, buildSystemConfig);
|
||||
bool passed = true;
|
||||
try {
|
||||
passed = await buildInstance.invokeTarget(target);
|
||||
} finally {
|
||||
// Always persist the file cache to disk.
|
||||
fileCache.persist();
|
||||
}
|
||||
return BuildResult(
|
||||
passed,
|
||||
buildInstance.exceptionMeasurements,
|
||||
buildInstance.stepTimings,
|
||||
);
|
||||
}
|
||||
|
||||
/// Describe the target `name` and all of its dependencies.
|
||||
List<Map<String, Object>> describe(
|
||||
String name,
|
||||
Environment environment,
|
||||
) {
|
||||
final Target target = _getNamedTarget(name);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
checkCycles(target);
|
||||
// Cheat a bit and re-use the same map.
|
||||
Map<String, Map<String, Object>> fold(Map<String, Map<String, Object>> accumulation, Target current) {
|
||||
accumulation[current.name] = current.toJson(environment);
|
||||
return accumulation;
|
||||
}
|
||||
|
||||
final Map<String, Map<String, Object>> result =
|
||||
<String, Map<String, Object>>{};
|
||||
final Map<String, Map<String, Object>> targets = target.fold(result, fold);
|
||||
return targets.values.toList();
|
||||
}
|
||||
|
||||
// Returns the corresponding target or throws.
|
||||
Target _getNamedTarget(String name) {
|
||||
final Target target = targets[name];
|
||||
if (target == null) {
|
||||
throw Exception('No registered target:$name.');
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/// An active instance of a build.
|
||||
class _BuildInstance {
|
||||
_BuildInstance(this.environment, this.fileCache, this.buildSystemConfig)
|
||||
: resourcePool = Pool(buildSystemConfig.resourcePoolSize ?? platform?.numberOfProcessors ?? 1);
|
||||
|
||||
final BuildSystemConfig buildSystemConfig;
|
||||
final Pool resourcePool;
|
||||
final Map<String, AsyncMemoizer<void>> pending = <String, AsyncMemoizer<void>>{};
|
||||
final Environment environment;
|
||||
final FileHashStore fileCache;
|
||||
|
||||
// Timings collected during target invocation.
|
||||
final Map<String, PerformanceMeasurement> stepTimings = <String, PerformanceMeasurement>{};
|
||||
|
||||
// Exceptions caught during the build process.
|
||||
final Map<String, ExceptionMeasurement> exceptionMeasurements = <String, ExceptionMeasurement>{};
|
||||
|
||||
Future<bool> invokeTarget(Target target) async {
|
||||
final List<bool> results = await Future.wait(target.dependencies.map(invokeTarget));
|
||||
if (results.any((bool result) => !result)) {
|
||||
return false;
|
||||
}
|
||||
final AsyncMemoizer<bool> memoizer = pending[target.name] ??= AsyncMemoizer<bool>();
|
||||
return memoizer.runOnce(() => _invokeInternal(target));
|
||||
}
|
||||
|
||||
Future<bool> _invokeInternal(Target target) async {
|
||||
final PoolResource resource = await resourcePool.request();
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
bool passed = true;
|
||||
bool skipped = false;
|
||||
try {
|
||||
final List<File> inputs = target.resolveInputs(environment);
|
||||
final Map<String, ChangeType> updates = await target.computeChanges(inputs, environment, fileCache);
|
||||
if (updates.isEmpty) {
|
||||
skipped = true;
|
||||
printStatus('Skipping target: ${target.name}');
|
||||
} else {
|
||||
printStatus('${target.name}: Starting');
|
||||
// build actions may be null.
|
||||
await target?.buildAction(updates, environment);
|
||||
printStatus('${target.name}: Complete');
|
||||
|
||||
final List<File> outputs = target.resolveOutputs(environment);
|
||||
// Update hashes for output files.
|
||||
await fileCache.hashFiles(outputs);
|
||||
target._writeStamp(inputs, outputs, environment);
|
||||
}
|
||||
} catch (exception, stackTrace) {
|
||||
// TODO(jonahwilliams): test
|
||||
target.clearStamp(environment);
|
||||
passed = false;
|
||||
skipped = false;
|
||||
exceptionMeasurements[target.name] = ExceptionMeasurement(
|
||||
target.name, exception, stackTrace);
|
||||
} finally {
|
||||
resource.release();
|
||||
stopwatch.stop();
|
||||
stepTimings[target.name] = PerformanceMeasurement(
|
||||
target.name, stopwatch.elapsedMilliseconds, skipped, passed);
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper class to collect exceptions.
|
||||
class ExceptionMeasurement {
|
||||
ExceptionMeasurement(this.target, this.exception, this.stackTrace);
|
||||
|
||||
final String target;
|
||||
final dynamic exception;
|
||||
final StackTrace stackTrace;
|
||||
}
|
||||
|
||||
/// Helper class to collect measurement data.
|
||||
class PerformanceMeasurement {
|
||||
PerformanceMeasurement(this.target, this.elapsedMilliseconds, this.skiped, this.passed);
|
||||
final int elapsedMilliseconds;
|
||||
final String target;
|
||||
final bool skiped;
|
||||
final bool passed;
|
||||
}
|
||||
|
||||
/// Check if there are any dependency cycles in the target.
|
||||
///
|
||||
/// Throws a [CycleException] if one is encountered.
|
||||
void checkCycles(Target initial) {
|
||||
void checkInternal(Target target, Set<Target> visited, Set<Target> stack) {
|
||||
if (stack.contains(target)) {
|
||||
throw CycleException(stack..add(target));
|
||||
}
|
||||
if (visited.contains(target)) {
|
||||
return;
|
||||
}
|
||||
visited.add(target);
|
||||
stack.add(target);
|
||||
for (Target dependency in target.dependencies) {
|
||||
checkInternal(dependency, visited, stack);
|
||||
}
|
||||
stack.remove(target);
|
||||
}
|
||||
checkInternal(initial, <Target>{}, <Target>{});
|
||||
}
|
||||
|
||||
/// Verifies that all files exist and are in a subdirectory of [Environment.buildDir].
|
||||
void verifyOutputDirectories(List<File> outputs, Environment environment, Target target) {
|
||||
final String buildDirectory = environment.buildDir.resolveSymbolicLinksSync();
|
||||
final String projectDirectory = environment.projectDir.resolveSymbolicLinksSync();
|
||||
final List<File> missingOutputs = <File>[];
|
||||
for (File sourceFile in outputs) {
|
||||
if (!sourceFile.existsSync()) {
|
||||
missingOutputs.add(sourceFile);
|
||||
continue;
|
||||
}
|
||||
final String path = sourceFile.resolveSymbolicLinksSync();
|
||||
if (!path.startsWith(buildDirectory) && !path.startsWith(projectDirectory)) {
|
||||
throw MisplacedOutputException(path, target.name);
|
||||
}
|
||||
}
|
||||
if (missingOutputs.isNotEmpty) {
|
||||
throw MissingOutputException(missingOutputs, target.name);
|
||||
}
|
||||
}
|
95
packages/flutter_tools/lib/src/build_system/exceptions.dart
Normal file
95
packages/flutter_tools/lib/src/build_system/exceptions.dart
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../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';
|
||||
}
|
||||
}
|
111
packages/flutter_tools/lib/src/build_system/file_hash_store.dart
Normal file
111
packages/flutter_tools/lib/src/build_system/file_hash_store.dart
Normal file
|
@ -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 '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);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
///
|
||||
// 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);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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'},
|
||||
],
|
||||
};
|
18
packages/flutter_tools/lib/src/build_system/filecache.proto
Normal file
18
packages/flutter_tools/lib/src/build_system/filecache.proto
Normal file
|
@ -0,0 +1,18 @@
|
|||
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;
|
||||
}
|
228
packages/flutter_tools/lib/src/build_system/source.dart
Normal file
228
packages/flutter_tools/lib/src/build_system/source.dart
Normal file
|
@ -0,0 +1,228 @@
|
|||
// 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;
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package: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,
|
||||
);
|
283
packages/flutter_tools/lib/src/build_system/targets/dart.dart
Normal file
283
packages/flutter_tools/lib/src/build_system/targets/dart.dart
Normal file
|
@ -0,0 +1,283 @@
|
|||
// 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/build.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/io.dart';
|
||||
import '../../base/platform.dart';
|
||||
import '../../base/process_manager.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../compile.dart';
|
||||
import '../../dart/package_map.dart';
|
||||
import '../../globals.dart';
|
||||
import '../../project.dart';
|
||||
import '../build_system.dart';
|
||||
import '../exceptions.dart';
|
||||
|
||||
/// The define to pass a [BuildMode].
|
||||
const String kBuildMode= 'BuildMode';
|
||||
|
||||
/// The define to pass whether we compile 64-bit android-arm code.
|
||||
const String kTargetPlatform = 'TargetPlatform';
|
||||
|
||||
/// The define to control what target file is used.
|
||||
const String kTargetFile = 'TargetFile';
|
||||
|
||||
/// The define to control what iOS architectures are built for.
|
||||
///
|
||||
/// This is expected to be a comma-separated list of architectures. If not
|
||||
/// provided, defaults to arm64.
|
||||
///
|
||||
/// The other supported value is armv7, the 32-bit iOS architecture.
|
||||
const String kIosArchs = 'IosArchs';
|
||||
|
||||
/// Supports compiling dart source to kernel with a subset of flags.
|
||||
///
|
||||
/// This is a non-incremental compile so the specific [updates] are ignored.
|
||||
Future<void> compileKernel(Map<String, ChangeType> updates, Environment environment) async {
|
||||
final KernelCompiler compiler = await kernelCompilerFactory.create(
|
||||
FlutterProject.fromDirectory(environment.projectDir),
|
||||
);
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
throw MissingDefineException(kBuildMode, 'kernel_snapshot');
|
||||
}
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final String targetFile = environment.defines[kTargetFile] ?? fs.path.join('lib', 'main.dart');
|
||||
|
||||
final CompilerOutput output = await compiler.compile(
|
||||
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
|
||||
aot: buildMode != BuildMode.debug,
|
||||
trackWidgetCreation: false,
|
||||
targetModel: TargetModel.flutter,
|
||||
targetProductVm: buildMode == BuildMode.release,
|
||||
outputFilePath: environment
|
||||
.buildDir
|
||||
.childFile('main.app.dill')
|
||||
.path,
|
||||
depFilePath: null,
|
||||
mainPath: targetFile,
|
||||
);
|
||||
if (output.errorCount != 0) {
|
||||
throw Exception('Errors during snapshot creation: $output');
|
||||
}
|
||||
}
|
||||
|
||||
/// Supports compiling a dart kernel file to an ELF binary.
|
||||
Future<void> compileAotElf(Map<String, ChangeType> updates, Environment environment) async {
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
|
||||
final String outputPath = environment.buildDir.path;
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
throw MissingDefineException(kBuildMode, 'aot_elf');
|
||||
}
|
||||
if (environment.defines[kTargetPlatform] == null) {
|
||||
throw MissingDefineException(kTargetPlatform, 'aot_elf');
|
||||
}
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('main.app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the locations of all dart files within the project.
|
||||
///
|
||||
/// This does not attempt to determine if a file is used or imported, so it
|
||||
/// may otherwise report more files than strictly necessary.
|
||||
List<File> listDartSources(Environment environment) {
|
||||
final Map<String, Uri> packageMap = PackageMap(environment.projectDir.childFile('.packages').path).map;
|
||||
final List<File> dartFiles = <File>[];
|
||||
for (Uri uri in packageMap.values) {
|
||||
final Directory libDirectory = fs.directory(uri.toFilePath(windows: platform.isWindows));
|
||||
for (FileSystemEntity entity in libDirectory.listSync(recursive: true)) {
|
||||
if (entity is File && entity.path.endsWith('.dart')) {
|
||||
dartFiles.add(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dartFiles;
|
||||
}
|
||||
|
||||
/// Supports compiling a dart kernel file to an assembly file.
|
||||
///
|
||||
/// If more than one iOS arch is provided, then this rule will
|
||||
/// produce a univeral binary.
|
||||
Future<void> compileAotAssembly(Map<String, ChangeType> updates, Environment environment) async {
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
|
||||
final String outputPath = environment.buildDir.path;
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
throw MissingDefineException(kBuildMode, 'aot_assembly');
|
||||
}
|
||||
if (environment.defines[kTargetPlatform] == null) {
|
||||
throw MissingDefineException(kTargetPlatform, 'aot_assembly');
|
||||
}
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final List<IOSArch> iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList()
|
||||
?? <IOSArch>[IOSArch.arm64];
|
||||
if (targetPlatform != TargetPlatform.ios) {
|
||||
throw Exception('aot_assembly is only supported for iOS applications');
|
||||
}
|
||||
|
||||
// If we're building for a single architecture (common), then skip the lipo.
|
||||
if (iosArchs.length == 1) {
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('main.app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
iosArch: iosArchs.single,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
}
|
||||
} else {
|
||||
// If we're building multiple iOS archs the binaries need to be lipo'd
|
||||
// together.
|
||||
final List<Future<int>> pending = <Future<int>>[];
|
||||
for (IOSArch iosArch in iosArchs) {
|
||||
pending.add(snapshotter.build(
|
||||
platform: targetPlatform,
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('main.app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: fs.path.join(outputPath, getNameForIOSArch(iosArch)),
|
||||
iosArch: iosArch,
|
||||
));
|
||||
}
|
||||
final List<int> results = await Future.wait(pending);
|
||||
if (results.any((int result) => result != 0)) {
|
||||
throw Exception('AOT snapshotter exited with code ${results.join()}');
|
||||
}
|
||||
final ProcessResult result = await processManager.run(<String>[
|
||||
'lipo',
|
||||
...iosArchs.map((IOSArch iosArch) =>
|
||||
fs.path.join(outputPath, getNameForIOSArch(iosArch), 'App.framework', 'App')),
|
||||
'-create',
|
||||
'-output',
|
||||
fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
]);
|
||||
if (result.exitCode != 0) {
|
||||
throw Exception('lipo exited with code ${result.exitCode}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a snapshot of the dart code used in the program.
|
||||
const Target kernelSnapshot = Target(
|
||||
name: 'kernel_snapshot',
|
||||
inputs: <Source>[
|
||||
Source.function(listDartSources), // <- every dart file under {PROJECT_DIR}/lib and in .packages
|
||||
Source.artifact(Artifact.platformKernelDill),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.frontendServerSnapshotForEngineDartSdk),
|
||||
],
|
||||
outputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/main.app.dill'),
|
||||
],
|
||||
dependencies: <Target>[],
|
||||
buildAction: compileKernel,
|
||||
);
|
||||
|
||||
/// Generate an ELF binary from a dart kernel file in profile mode.
|
||||
const Target aotElfProfile = Target(
|
||||
name: 'aot_elf_profile',
|
||||
inputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/main.app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
Source.artifact(Artifact.genSnapshot,
|
||||
platform: TargetPlatform.android_arm,
|
||||
mode: BuildMode.profile,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/app.so'),
|
||||
],
|
||||
dependencies: <Target>[
|
||||
kernelSnapshot,
|
||||
],
|
||||
buildAction: compileAotElf,
|
||||
);
|
||||
|
||||
/// Generate an ELF binary from a dart kernel file in release mode.
|
||||
const Target aotElfRelease= Target(
|
||||
name: 'aot_elf_release',
|
||||
inputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/main.app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
Source.artifact(Artifact.genSnapshot,
|
||||
platform: TargetPlatform.android_arm,
|
||||
mode: BuildMode.release,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/app.so'),
|
||||
],
|
||||
dependencies: <Target>[
|
||||
kernelSnapshot,
|
||||
],
|
||||
buildAction: compileAotElf,
|
||||
);
|
||||
|
||||
/// Generate an assembly target from a dart kernel file in profile mode.
|
||||
const Target aotAssemblyProfile = Target(
|
||||
name: 'aot_assembly_profile',
|
||||
inputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/main.app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
Source.artifact(Artifact.genSnapshot,
|
||||
platform: TargetPlatform.ios,
|
||||
mode: BuildMode.profile,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
// TODO(jonahwilliams): are these used or just a side effect?
|
||||
// Source.pattern('{BUILD_DIR}/snapshot_assembly.S'),
|
||||
// Source.pattern('{BUILD_DIR}/snapshot_assembly.o'),
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
],
|
||||
dependencies: <Target>[
|
||||
kernelSnapshot,
|
||||
],
|
||||
buildAction: compileAotAssembly,
|
||||
);
|
||||
|
||||
/// Generate an assembly target from a dart kernel file in release mode.
|
||||
const Target aotAssemblyRelease = Target(
|
||||
name: 'aot_assembly_release',
|
||||
inputs: <Source>[
|
||||
Source.pattern('{BUILD_DIR}/main.app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
Source.artifact(Artifact.genSnapshot,
|
||||
platform: TargetPlatform.ios,
|
||||
mode: BuildMode.release,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
// TODO(jonahwilliams): are these used or just a side effect?
|
||||
// Source.pattern('{BUILD_DIR}/snapshot_assembly.S'),
|
||||
// Source.pattern('{BUILD_DIR}/snapshot_assembly.o'),
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
],
|
||||
dependencies: <Target>[
|
||||
kernelSnapshot,
|
||||
],
|
||||
buildAction: compileAotAssembly,
|
||||
);
|
43
packages/flutter_tools/lib/src/build_system/targets/ios.dart
Normal file
43
packages/flutter_tools/lib/src/build_system/targets/ios.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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,
|
||||
]
|
||||
);
|
|
@ -0,0 +1,46 @@
|
|||
// 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,
|
||||
);
|
100
packages/flutter_tools/lib/src/build_system/targets/macos.dart
Normal file
100
packages/flutter_tools/lib/src/build_system/targets/macos.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
// 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,
|
||||
]
|
||||
);
|
|
@ -0,0 +1,50 @@
|
|||
// 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,
|
||||
);
|
221
packages/flutter_tools/lib/src/commands/assemble.dart
Normal file
221
packages/flutter_tools/lib/src/commands/assemble.dart
Normal file
|
@ -0,0 +1,221 @@
|
|||
// 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"
|
||||
|
|
554
packages/flutter_tools/test/build_system/build_system_test.dart
Normal file
554
packages/flutter_tools/test/build_system/build_system_test.dart
Normal file
|
@ -0,0 +1,554 @@
|
|||
// 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/artifacts.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/file_hash_store.dart';
|
||||
import 'package:flutter_tools/src/build_system/filecache.pb.dart' as pb;
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/convert.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
group(Target, () {
|
||||
Testbed testbed;
|
||||
MockPlatform mockPlatform;
|
||||
Environment environment;
|
||||
Target fooTarget;
|
||||
Target barTarget;
|
||||
Target fizzTarget;
|
||||
BuildSystem buildSystem;
|
||||
int fooInvocations;
|
||||
int barInvocations;
|
||||
|
||||
setUp(() {
|
||||
fooInvocations = 0;
|
||||
barInvocations = 0;
|
||||
mockPlatform = MockPlatform();
|
||||
// Keep file paths the same.
|
||||
when(mockPlatform.isWindows).thenReturn(false);
|
||||
testbed = Testbed(
|
||||
setup: () {
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
);
|
||||
fs.file('foo.dart').createSync(recursive: true);
|
||||
fs.file('pubspec.yaml').createSync();
|
||||
fooTarget = Target(
|
||||
name: 'foo',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
],
|
||||
dependencies: <Target>[],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
environment
|
||||
.buildDir
|
||||
.childFile('out')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('hey');
|
||||
fooInvocations++;
|
||||
}
|
||||
);
|
||||
barTarget = Target(
|
||||
name: 'bar',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/bar'),
|
||||
],
|
||||
dependencies: <Target>[fooTarget],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
environment.buildDir
|
||||
.childFile('bar')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('there');
|
||||
barInvocations++;
|
||||
}
|
||||
);
|
||||
fizzTarget = Target(
|
||||
name: 'fizz',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/fizz'),
|
||||
],
|
||||
dependencies: <Target>[fooTarget],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
throw Exception('something bad happens');
|
||||
}
|
||||
);
|
||||
buildSystem = BuildSystem(<String, Target>{
|
||||
fooTarget.name: fooTarget,
|
||||
barTarget.name: barTarget,
|
||||
fizzTarget.name: fizzTarget,
|
||||
});
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Platform: () => mockPlatform,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('can describe build rules', () => testbed.run(() {
|
||||
expect(buildSystem.describe('foo', environment), <Object>[
|
||||
<String, Object>{
|
||||
'name': 'foo',
|
||||
'dependencies': <String>[],
|
||||
'inputs': <String>['/foo.dart'],
|
||||
'outputs': <String>[fs.path.join(environment.buildDir.path, 'out')],
|
||||
'stamp': fs.path.join(environment.buildDir.path, 'foo.stamp'),
|
||||
}
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Throws exception if asked to build non-existent target', () => testbed.run(() {
|
||||
expect(buildSystem.build('not_real', environment, const BuildSystemConfig()), throwsA(isInstanceOf<Exception>()));
|
||||
}));
|
||||
|
||||
test('Throws exception if asked to build with missing inputs', () => testbed.run(() async {
|
||||
// Delete required input file.
|
||||
fs.file('foo.dart').deleteSync();
|
||||
final BuildResult buildResult = await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
expect(buildResult.hasException, true);
|
||||
expect(buildResult.exceptions.values.single.exception, isInstanceOf<MissingInputException>());
|
||||
}));
|
||||
|
||||
test('Throws exception if it does not produce a specified output', () => testbed.run(() async {
|
||||
final Target badTarget = Target
|
||||
(buildAction: (Map<String, ChangeType> inputs, Environment environment) {},
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out')
|
||||
],
|
||||
name: 'bad'
|
||||
);
|
||||
buildSystem = BuildSystem(<String, Target>{
|
||||
badTarget.name: badTarget,
|
||||
});
|
||||
final BuildResult result = await buildSystem.build('bad', environment, const BuildSystemConfig());
|
||||
|
||||
expect(result.hasException, true);
|
||||
expect(result.exceptions.values.single.exception, isInstanceOf<MissingOutputException>());
|
||||
}));
|
||||
|
||||
test('Saves a stamp file with inputs and outputs', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
final File stampFile = fs.file(fs.path.join(environment.buildDir.path, 'foo.stamp'));
|
||||
expect(stampFile.existsSync(), true);
|
||||
|
||||
final Map<String, Object> stampContents = json.decode(stampFile.readAsStringSync());
|
||||
expect(stampContents['inputs'], <Object>['/foo.dart']);
|
||||
}));
|
||||
|
||||
test('Does not re-invoke build if stamp is valid', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
test('Re-invoke build if input is modified', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
fs.file('foo.dart').writeAsStringSync('new contents');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
expect(fooInvocations, 2);
|
||||
}));
|
||||
|
||||
test('does not re-invoke build if input timestamp changes', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
fs.file('foo.dart').writeAsStringSync('');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
test('does not re-invoke build if output timestamp changes', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
environment.buildDir.childFile('out').writeAsStringSync('hey');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
|
||||
test('Re-invoke build if output is modified', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
|
||||
environment.buildDir.childFile('out').writeAsStringSync('Something different');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
expect(fooInvocations, 2);
|
||||
}));
|
||||
|
||||
test('Runs dependencies of targets', () => testbed.run(() async {
|
||||
await buildSystem.build('bar', environment, const BuildSystemConfig());
|
||||
|
||||
expect(fs.file(fs.path.join(environment.buildDir.path, 'bar')).existsSync(), true);
|
||||
expect(fooInvocations, 1);
|
||||
expect(barInvocations, 1);
|
||||
}));
|
||||
|
||||
test('handles a throwing build action', () => testbed.run(() async {
|
||||
final BuildResult result = await buildSystem.build('fizz', environment, const BuildSystemConfig());
|
||||
|
||||
expect(result.hasException, true);
|
||||
}));
|
||||
|
||||
test('Can describe itself with JSON output', () => testbed.run(() {
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
expect(fooTarget.toJson(environment), <String, dynamic>{
|
||||
'inputs': <Object>[
|
||||
'/foo.dart'
|
||||
],
|
||||
'outputs': <Object>[
|
||||
fs.path.join(environment.buildDir.path, 'out'),
|
||||
],
|
||||
'dependencies': <Object>[],
|
||||
'name': 'foo',
|
||||
'stamp': fs.path.join(environment.buildDir.path, 'foo.stamp'),
|
||||
});
|
||||
}));
|
||||
|
||||
test('Compute update recognizes added files', () => testbed.run(() async {
|
||||
fs.directory('build').createSync();
|
||||
final FileHashStore fileCache = FileHashStore(environment);
|
||||
fileCache.initialize();
|
||||
final List<File> inputs = fooTarget.resolveInputs(environment);
|
||||
final Map<String, ChangeType> changes = await fooTarget.computeChanges(inputs, environment, fileCache);
|
||||
fileCache.persist();
|
||||
|
||||
expect(changes, <String, ChangeType>{
|
||||
'/foo.dart': ChangeType.Added
|
||||
});
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
final Map<String, ChangeType> secondChanges = await fooTarget.computeChanges(inputs, environment, fileCache);
|
||||
|
||||
expect(secondChanges, <String, ChangeType>{});
|
||||
}));
|
||||
});
|
||||
|
||||
group('FileCache', () {
|
||||
Testbed testbed;
|
||||
Environment environment;
|
||||
|
||||
setUp(() {
|
||||
testbed = Testbed(setup: () {
|
||||
fs.directory('build').createSync();
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('Initializes file cache', () => testbed.run(() {
|
||||
final FileHashStore fileCache = FileHashStore(environment);
|
||||
fileCache.initialize();
|
||||
fileCache.persist();
|
||||
|
||||
expect(fs.file(fs.path.join('build', '.filecache')).existsSync(), true);
|
||||
|
||||
final List<int> buffer = fs.file(fs.path.join('build', '.filecache')).readAsBytesSync();
|
||||
final pb.FileStorage fileStorage = pb.FileStorage.fromBuffer(buffer);
|
||||
|
||||
expect(fileStorage.files, isEmpty);
|
||||
expect(fileStorage.version, 1);
|
||||
}));
|
||||
|
||||
test('saves and restores to file cache', () => testbed.run(() {
|
||||
final File file = fs.file('foo.dart')
|
||||
..createSync()
|
||||
..writeAsStringSync('hello');
|
||||
final FileHashStore fileCache = FileHashStore(environment);
|
||||
fileCache.initialize();
|
||||
fileCache.hashFiles(<File>[file]);
|
||||
fileCache.persist();
|
||||
final String currentHash = fileCache.currentHashes[file.resolveSymbolicLinksSync()];
|
||||
final List<int> buffer = fs.file(fs.path.join('build', '.filecache')).readAsBytesSync();
|
||||
pb.FileStorage fileStorage = pb.FileStorage.fromBuffer(buffer);
|
||||
|
||||
expect(fileStorage.files.single.hash, currentHash);
|
||||
expect(fileStorage.files.single.path, file.resolveSymbolicLinksSync());
|
||||
|
||||
|
||||
final FileHashStore newFileCache = FileHashStore(environment);
|
||||
newFileCache.initialize();
|
||||
expect(newFileCache.currentHashes, isEmpty);
|
||||
expect(newFileCache.previousHashes[fs.path.absolute('foo.dart')], currentHash);
|
||||
newFileCache.persist();
|
||||
|
||||
// Still persisted correctly.
|
||||
fileStorage = pb.FileStorage.fromBuffer(buffer);
|
||||
|
||||
expect(fileStorage.files.single.hash, currentHash);
|
||||
expect(fileStorage.files.single.path, file.resolveSymbolicLinksSync());
|
||||
}));
|
||||
});
|
||||
|
||||
group('Target', () {
|
||||
Testbed testbed;
|
||||
MockPlatform mockPlatform;
|
||||
Environment environment;
|
||||
Target sharedTarget;
|
||||
BuildSystem buildSystem;
|
||||
int shared;
|
||||
|
||||
setUp(() {
|
||||
shared = 0;
|
||||
Cache.flutterRoot = '';
|
||||
mockPlatform = MockPlatform();
|
||||
// Keep file paths the same.
|
||||
when(mockPlatform.isWindows).thenReturn(false);
|
||||
when(mockPlatform.isLinux).thenReturn(true);
|
||||
when(mockPlatform.isMacOS).thenReturn(false);
|
||||
testbed = Testbed(
|
||||
setup: () {
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
);
|
||||
fs.file('foo.dart').createSync(recursive: true);
|
||||
fs.file('pubspec.yaml').createSync();
|
||||
sharedTarget = Target(
|
||||
name: 'shared',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
],
|
||||
outputs: const <Source>[],
|
||||
dependencies: <Target>[],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
shared += 1;
|
||||
}
|
||||
);
|
||||
final Target fooTarget = Target(
|
||||
name: 'foo',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
],
|
||||
dependencies: <Target>[sharedTarget],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
environment
|
||||
.buildDir
|
||||
.childFile('out')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('hey');
|
||||
}
|
||||
);
|
||||
final Target barTarget = Target(
|
||||
name: 'bar',
|
||||
inputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/bar'),
|
||||
],
|
||||
dependencies: <Target>[fooTarget, sharedTarget],
|
||||
buildAction: (Map<String, ChangeType> updates, Environment environment) {
|
||||
environment
|
||||
.buildDir
|
||||
.childFile('bar')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('there');
|
||||
}
|
||||
);
|
||||
buildSystem = BuildSystem(<String, Target>{
|
||||
fooTarget.name: fooTarget,
|
||||
barTarget.name: barTarget,
|
||||
sharedTarget.name: sharedTarget,
|
||||
});
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Platform: () => mockPlatform,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Only invokes shared target once', () => testbed.run(() async {
|
||||
await buildSystem.build('bar', environment, const BuildSystemConfig());
|
||||
|
||||
expect(shared, 1);
|
||||
}));
|
||||
});
|
||||
|
||||
group('Source', () {
|
||||
Testbed testbed;
|
||||
SourceVisitor visitor;
|
||||
Environment environment;
|
||||
|
||||
setUp(() {
|
||||
testbed = Testbed(setup: () {
|
||||
fs.directory('cache').createSync();
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
buildDir: fs.directory('build'),
|
||||
);
|
||||
visitor = SourceVisitor(environment);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
});
|
||||
});
|
||||
|
||||
test('configures implicit vs explict correctly', () => testbed.run(() {
|
||||
expect(const Source.pattern('{PROJECT_DIR}/foo').implicit, false);
|
||||
expect(const Source.pattern('{PROJECT_DIR}/*foo').implicit, true);
|
||||
expect(Source.function((Environment environment) => <File>[]).implicit, true);
|
||||
expect(Source.behavior(TestBehavior()).implicit, true);
|
||||
}));
|
||||
|
||||
test('can substitute {PROJECT_DIR}/foo', () => testbed.run(() {
|
||||
fs.file('foo').createSync();
|
||||
const Source fooSource = Source.pattern('{PROJECT_DIR}/foo');
|
||||
fooSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.path, fs.path.absolute('foo'));
|
||||
}));
|
||||
|
||||
test('can substitute {BUILD_DIR}/bar', () => testbed.run(() {
|
||||
final String path = fs.path.join(environment.buildDir.path, 'bar');
|
||||
fs.file(path).createSync();
|
||||
const Source barSource = Source.pattern('{BUILD_DIR}/bar');
|
||||
barSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.path, fs.path.absolute(path));
|
||||
}));
|
||||
|
||||
test('can substitute Artifact', () => testbed.run(() {
|
||||
final String path = fs.path.join(
|
||||
Cache.instance.getArtifactDirectory('engine').path,
|
||||
'windows-x64',
|
||||
'foo',
|
||||
);
|
||||
fs.file(path).createSync(recursive: true);
|
||||
const Source fizzSource = Source.artifact(Artifact.windowsDesktopPath, platform: TargetPlatform.windows_x64);
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.resolveSymbolicLinksSync(), fs.path.absolute(path));
|
||||
}));
|
||||
|
||||
test('can substitute {PROJECT_DIR}/*.fizz', () => testbed.run(() {
|
||||
const Source fizzSource = Source.pattern('{PROJECT_DIR}/*.fizz');
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources, isEmpty);
|
||||
|
||||
fs.file('foo.fizz').createSync();
|
||||
fs.file('foofizz').createSync();
|
||||
|
||||
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.path, fs.path.absolute('foo.fizz'));
|
||||
}));
|
||||
|
||||
test('can substitute {PROJECT_DIR}/fizz.*', () => testbed.run(() {
|
||||
const Source fizzSource = Source.pattern('{PROJECT_DIR}/fizz.*');
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources, isEmpty);
|
||||
|
||||
fs.file('fizz.foo').createSync();
|
||||
fs.file('fizz').createSync();
|
||||
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.path, fs.path.absolute('fizz.foo'));
|
||||
}));
|
||||
|
||||
|
||||
test('can substitute {PROJECT_DIR}/a*bc', () => testbed.run(() {
|
||||
const Source fizzSource = Source.pattern('{PROJECT_DIR}/bc*bc');
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources, isEmpty);
|
||||
|
||||
fs.file('bcbc').createSync();
|
||||
fs.file('bc').createSync();
|
||||
|
||||
fizzSource.accept(visitor);
|
||||
|
||||
expect(visitor.sources.single.path, fs.path.absolute('bcbc'));
|
||||
}));
|
||||
|
||||
|
||||
test('crashes on bad substitute of two **', () => testbed.run(() {
|
||||
const Source fizzSource = Source.pattern('{PROJECT_DIR}/*.*bar');
|
||||
|
||||
fs.file('abcd.bar').createSync();
|
||||
|
||||
expect(() => fizzSource.accept(visitor), throwsA(isInstanceOf<InvalidPatternException>()));
|
||||
}));
|
||||
|
||||
|
||||
test('can\'t substitute foo', () => testbed.run(() {
|
||||
const Source invalidBase = Source.pattern('foo');
|
||||
|
||||
expect(() => invalidBase.accept(visitor), throwsA(isInstanceOf<InvalidPatternException>()));
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('Can find dependency cycles', () {
|
||||
final Target barTarget = Target(
|
||||
name: 'bar',
|
||||
inputs: <Source>[],
|
||||
outputs: <Source>[],
|
||||
buildAction: null,
|
||||
dependencies: nonconst(<Target>[])
|
||||
);
|
||||
final Target fooTarget = Target(
|
||||
name: 'foo',
|
||||
inputs: <Source>[],
|
||||
outputs: <Source>[],
|
||||
buildAction: null,
|
||||
dependencies: nonconst(<Target>[])
|
||||
);
|
||||
barTarget.dependencies.add(fooTarget);
|
||||
fooTarget.dependencies.add(barTarget);
|
||||
expect(() => checkCycles(barTarget), throwsA(isInstanceOf<CycleException>()));
|
||||
});
|
||||
}
|
||||
|
||||
class MockPlatform extends Mock implements Platform {}
|
||||
|
||||
// Work-around for silly lint check.
|
||||
T nonconst<T>(T input) => input;
|
||||
|
||||
class TestBehavior extends SourceBehavior {
|
||||
@override
|
||||
List<File> inputs(Environment environment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<File> outputs(Environment environment) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// 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'
|
||||
);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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);
|
||||
}));
|
||||
});
|
||||
}
|
224
packages/flutter_tools/test/build_system/targets/dart_test.dart
Normal file
224
packages/flutter_tools/test/build_system/targets/dart_test.dart
Normal file
|
@ -0,0 +1,224 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '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 {}
|
115
packages/flutter_tools/test/build_system/targets/macos_test.dart
Normal file
115
packages/flutter_tools/test/build_system/targets/macos_test.dart
Normal file
|
@ -0,0 +1,115 @@
|
|||
// 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 = '';
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// 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 {}
|
84
packages/flutter_tools/test/commands/assemble_test.dart
Normal file
84
packages/flutter_tools/test/commands/assemble_test.dart
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '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.
|
||||
|
|
Loading…
Reference in a new issue