mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Rearrange flutter assemble implementation (#36240)
This commit is contained in:
parent
c469b1fea9
commit
188093c912
@ -18,30 +18,9 @@ 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].
|
||||
@ -102,7 +81,7 @@ class BuildSystemConfig {
|
||||
/// Example: aot_elf has a dependency on the dill and packages file
|
||||
/// produced by the kernel_snapshot step.
|
||||
///
|
||||
/// ### Targest should declare all outputs produced
|
||||
/// ### Targets 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.
|
||||
@ -116,43 +95,38 @@ class BuildSystemConfig {
|
||||
/// 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>[],
|
||||
});
|
||||
|
||||
abstract class Target {
|
||||
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;
|
||||
String get name;
|
||||
|
||||
/// The dependencies of this target.
|
||||
final List<Target> dependencies;
|
||||
List<Target> get dependencies;
|
||||
|
||||
/// The input [Source]s which are diffed to determine if a target should run.
|
||||
final List<Source> inputs;
|
||||
List<Source> get inputs;
|
||||
|
||||
/// The output [Source]s which we attempt to verify are correctly produced.
|
||||
final List<Source> outputs;
|
||||
List<Source> get outputs;
|
||||
|
||||
/// The action which performs this build step.
|
||||
final BuildAction buildAction;
|
||||
Future<void> build(List<File> inputFiles, Environment environment);
|
||||
|
||||
/// Collect hashes for all inputs to determine if any have changed.
|
||||
Future<Map<String, ChangeType>> computeChanges(
|
||||
///
|
||||
/// Returns whether this target can be skipped.
|
||||
Future<bool> 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>[];
|
||||
bool canSkip = true;
|
||||
|
||||
// If the stamp file doesn't exist, we haven't run this step before and
|
||||
// all inputs were added.
|
||||
@ -161,6 +135,8 @@ class Target {
|
||||
// Something went wrong writing the stamp file.
|
||||
if (content == null || content.isEmpty) {
|
||||
stamp.deleteSync();
|
||||
// Malformed stamp file, not safe to skip.
|
||||
canSkip = false;
|
||||
} else {
|
||||
final Map<String, Object> values = json.decode(content);
|
||||
final List<Object> inputs = values['inputs'];
|
||||
@ -168,12 +144,13 @@ class Target {
|
||||
inputs.cast<String>().forEach(previousInputs.add);
|
||||
outputs.cast<String>().forEach(previousOutputs.add);
|
||||
}
|
||||
} else {
|
||||
// No stamp file, not safe to skip.
|
||||
canSkip = false;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// For each input, first determine if we've already computed the hash
|
||||
// for it. Then 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) {
|
||||
@ -187,19 +164,19 @@ class Target {
|
||||
if (fileHashStore.currentHashes.containsKey(absolutePath)) {
|
||||
final String currentHash = fileHashStore.currentHashes[absolutePath];
|
||||
if (currentHash != previousHash) {
|
||||
updates[absolutePath] = previousInputs.contains(absolutePath)
|
||||
? ChangeType.Modified
|
||||
: ChangeType.Added;
|
||||
canSkip = false;
|
||||
}
|
||||
} else {
|
||||
sourcesToHash.add(file);
|
||||
}
|
||||
}
|
||||
// Check if any outputs were deleted or modified from the previous run.
|
||||
|
||||
// For each outut, first determine if we've already computed the hash
|
||||
// for it. Then collect it to be sent off for hashing as a group.
|
||||
for (String previousOutput in previousOutputs) {
|
||||
final File file = fs.file(previousOutput);
|
||||
if (!file.existsSync()) {
|
||||
updates[previousOutput] = ChangeType.Removed;
|
||||
canSkip = false;
|
||||
continue;
|
||||
}
|
||||
final String absolutePath = file.resolveSymbolicLinksSync();
|
||||
@ -207,15 +184,14 @@ class Target {
|
||||
if (fileHashStore.currentHashes.containsKey(absolutePath)) {
|
||||
final String currentHash = fileHashStore.currentHashes[absolutePath];
|
||||
if (currentHash != previousHash) {
|
||||
updates[absolutePath] = previousInputs.contains(absolutePath)
|
||||
? ChangeType.Modified
|
||||
: ChangeType.Added;
|
||||
canSkip = false;
|
||||
}
|
||||
} else {
|
||||
sourcesToHash.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
// If we depend on a file that doesnt exist on disk, kill the build.
|
||||
if (missingInputs.isNotEmpty) {
|
||||
throw MissingInputException(missingInputs, name);
|
||||
}
|
||||
@ -224,24 +200,11 @@ class Target {
|
||||
// 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;
|
||||
if (dirty.isNotEmpty) {
|
||||
canSkip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
return canSkip;
|
||||
}
|
||||
|
||||
/// Invoke to remove the stamp file if the [buildAction] threw an exception;
|
||||
@ -413,6 +376,7 @@ class Environment {
|
||||
rootBuildDir: rootBuildDir,
|
||||
cacheDir: Cache.instance.getRoot(),
|
||||
defines: defines,
|
||||
flutterRootDir: fs.directory(Cache.flutterRoot),
|
||||
);
|
||||
}
|
||||
|
||||
@ -422,6 +386,7 @@ class Environment {
|
||||
@required this.rootBuildDir,
|
||||
@required this.cacheDir,
|
||||
@required this.defines,
|
||||
@required this.flutterRootDir,
|
||||
});
|
||||
|
||||
/// The [Source] value which is substituted with the path to [projectDir].
|
||||
@ -454,6 +419,11 @@ class Environment {
|
||||
/// the flutter tool.
|
||||
final Directory cacheDir;
|
||||
|
||||
/// The `FLUTTER_ROOT` environment variable.
|
||||
///
|
||||
/// Defaults to to the value of [Cache.flutterRoot].
|
||||
final Directory flutterRootDir;
|
||||
|
||||
/// Additional configuration passed to the build targets.
|
||||
///
|
||||
/// Setting values here forces a unique build directory to be chosen
|
||||
@ -477,36 +447,14 @@ class BuildResult {
|
||||
|
||||
/// The build system is responsible for invoking and ordering [Target]s.
|
||||
class BuildSystem {
|
||||
BuildSystem([Map<String, Target> targets])
|
||||
: targets = targets ?? _defaultTargets;
|
||||
const BuildSystem();
|
||||
|
||||
/// 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.
|
||||
/// Build `target` and all of its dependencies.
|
||||
Future<BuildResult> build(
|
||||
String name,
|
||||
Target target,
|
||||
Environment environment,
|
||||
BuildSystemConfig buildSystemConfig,
|
||||
{ BuildSystemConfig buildSystemConfig = const BuildSystemConfig() }
|
||||
) async {
|
||||
final Target target = _getNamedTarget(name);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
|
||||
// Load file hash store from previous builds.
|
||||
@ -530,35 +478,6 @@ class BuildSystem {
|
||||
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.
|
||||
@ -594,14 +513,13 @@ class _BuildInstance {
|
||||
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) {
|
||||
final bool canSkip = await target.computeChanges(inputs, environment, fileCache);
|
||||
if (canSkip) {
|
||||
skipped = true;
|
||||
printStatus('Skipping target: ${target.name}');
|
||||
} else {
|
||||
printStatus('${target.name}: Starting');
|
||||
// build actions may be null.
|
||||
await target?.buildAction(updates, environment);
|
||||
await target.build(inputs, environment);
|
||||
printStatus('${target.name}: Complete');
|
||||
|
||||
final List<File> outputs = target.resolveOutputs(environment);
|
||||
@ -610,7 +528,6 @@ class _BuildInstance {
|
||||
target._writeStamp(inputs, outputs, environment);
|
||||
}
|
||||
} catch (exception, stackTrace) {
|
||||
// TODO(jonahwilliams): test
|
||||
target.clearStamp(environment);
|
||||
passed = false;
|
||||
skipped = false;
|
||||
|
@ -65,8 +65,9 @@ class SourceVisitor {
|
||||
fs.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
|
||||
break;
|
||||
case Environment.kFlutterRootDirectory:
|
||||
// flutter root will not contain a symbolic link.
|
||||
segments.addAll(
|
||||
fs.path.split(environment.cacheDir.resolveSymbolicLinksSync()));
|
||||
fs.path.split(environment.flutterRootDir.absolute.path));
|
||||
break;
|
||||
default:
|
||||
throw InvalidPatternException(pattern);
|
||||
|
@ -10,10 +10,6 @@ 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();
|
||||
|
||||
@ -24,6 +20,8 @@ class AssetBehavior extends SourceBehavior {
|
||||
manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
);
|
||||
// Filter the file type to remove the files that are generated by this
|
||||
// command as inputs.
|
||||
final List<File> results = <File>[];
|
||||
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
|
||||
for (DevFSFileContent devFsContent in files) {
|
||||
@ -40,16 +38,41 @@ class AssetBehavior extends SourceBehavior {
|
||||
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));
|
||||
for (String key in assetBundle.entries.keys) {
|
||||
final File file = fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 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 {
|
||||
/// Copy the assets defined in the flutter manifest into a build directory.
|
||||
class CopyAssets extends Target {
|
||||
const CopyAssets();
|
||||
|
||||
@override
|
||||
String get name => 'copy_assets';
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[];
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
|
||||
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
|
||||
Source.behavior(AssetBehavior()),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <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.
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, Environment environment) async {
|
||||
final Directory output = environment
|
||||
.buildDir
|
||||
.childDirectory('flutter_assets');
|
||||
@ -75,21 +98,5 @@ Future<void> copyAssetsInvocation(Map<String, ChangeType> updates, Environment e
|
||||
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,
|
||||
);
|
||||
|
@ -5,9 +5,7 @@
|
||||
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';
|
||||
@ -36,63 +34,6 @@ const String kBitcodeFlag = 'EnableBitcode';
|
||||
/// 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,
|
||||
bitcode: false,
|
||||
);
|
||||
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
|
||||
@ -111,96 +52,99 @@ List<File> listDartSources(Environment environment) {
|
||||
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');
|
||||
}
|
||||
final bool bitcode = environment.defines[kBitcodeFlag] == 'true';
|
||||
|
||||
// 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,
|
||||
bitcode: bitcode,
|
||||
);
|
||||
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,
|
||||
bitcode: bitcode,
|
||||
));
|
||||
}
|
||||
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>[
|
||||
class KernelSnapshot extends Target {
|
||||
const KernelSnapshot();
|
||||
|
||||
@override
|
||||
String get name => 'kernel_snapshot';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/dart.dart'),
|
||||
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,
|
||||
);
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/app.dill'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => <Target>[];
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, 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('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.
|
||||
abstract class AotElfBase extends Target {
|
||||
const AotElfBase();
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, 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('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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'),
|
||||
class AotElfProfile extends AotElfBase {
|
||||
const AotElfProfile();
|
||||
|
||||
@override
|
||||
String get name => 'aot_elf_profile';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/dart.dart'),
|
||||
Source.pattern('{BUILD_DIR}/app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
@ -208,21 +152,30 @@ const Target aotElfProfile = Target(
|
||||
platform: TargetPlatform.android_arm,
|
||||
mode: BuildMode.profile,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/app.so'),
|
||||
],
|
||||
dependencies: <Target>[
|
||||
kernelSnapshot,
|
||||
],
|
||||
buildAction: compileAotElf,
|
||||
);
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[
|
||||
KernelSnapshot(),
|
||||
];
|
||||
}
|
||||
|
||||
/// 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'),
|
||||
class AotElfRelease extends AotElfBase {
|
||||
const AotElfRelease();
|
||||
|
||||
@override
|
||||
String get name => 'aot_elf_release';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/dart.dart'),
|
||||
Source.pattern('{BUILD_DIR}/app.dill'),
|
||||
Source.pattern('{PROJECT_DIR}/.packages'),
|
||||
Source.artifact(Artifact.engineDartBinary),
|
||||
Source.artifact(Artifact.skyEnginePath),
|
||||
@ -230,62 +183,15 @@ const Target aotElfRelease= Target(
|
||||
platform: TargetPlatform.android_arm,
|
||||
mode: BuildMode.release,
|
||||
),
|
||||
],
|
||||
outputs: <Source>[
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <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,
|
||||
);
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[
|
||||
KernelSnapshot(),
|
||||
];
|
||||
}
|
||||
|
@ -2,42 +2,149 @@
|
||||
// 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/process_manager.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../build_system.dart';
|
||||
import 'assets.dart';
|
||||
import '../exceptions.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,
|
||||
]
|
||||
);
|
||||
/// 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.
|
||||
abstract class AotAssemblyBase extends Target {
|
||||
const AotAssemblyBase();
|
||||
|
||||
/// Create an iOS profile application.
|
||||
const Target profileIosApplication = Target(
|
||||
name: 'profile_ios_application',
|
||||
buildAction: null,
|
||||
inputs: <Source>[],
|
||||
outputs: <Source>[],
|
||||
dependencies: <Target>[
|
||||
copyAssets,
|
||||
aotAssemblyProfile,
|
||||
]
|
||||
);
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, 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 bool bitcode = environment.defines[kBitcodeFlag] == 'true';
|
||||
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');
|
||||
}
|
||||
|
||||
/// Create an iOS debug application.
|
||||
const Target releaseIosApplication = Target(
|
||||
name: 'release_ios_application',
|
||||
buildAction: null,
|
||||
inputs: <Source>[],
|
||||
outputs: <Source>[],
|
||||
dependencies: <Target>[
|
||||
copyAssets,
|
||||
aotAssemblyRelease,
|
||||
]
|
||||
);
|
||||
// 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('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
iosArch: iosArchs.single,
|
||||
bitcode: bitcode,
|
||||
);
|
||||
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('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: fs.path.join(outputPath, getNameForIOSArch(iosArch)),
|
||||
iosArch: iosArch,
|
||||
bitcode: bitcode,
|
||||
));
|
||||
}
|
||||
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 an assembly target from a dart kernel file in release mode.
|
||||
class AotAssemblyRelease extends AotAssemblyBase {
|
||||
const AotAssemblyRelease();
|
||||
|
||||
@override
|
||||
String get name => 'aot_assembly_release';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
|
||||
Source.pattern('{BUILD_DIR}/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,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[
|
||||
KernelSnapshot(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/// Generate an assembly target from a dart kernel file in profile mode.
|
||||
class AotAssemblyProfile extends AotAssemblyBase {
|
||||
const AotAssemblyProfile();
|
||||
|
||||
@override
|
||||
String get name => 'aot_assembly_profile';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
|
||||
Source.pattern('{BUILD_DIR}/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,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[
|
||||
KernelSnapshot(),
|
||||
];
|
||||
}
|
||||
|
@ -7,32 +7,21 @@ 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>[
|
||||
class UnpackLinux extends Target {
|
||||
const UnpackLinux();
|
||||
|
||||
@override
|
||||
String get name => 'unpack_linux';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'),
|
||||
Source.artifact(Artifact.linuxDesktopPath),
|
||||
],
|
||||
outputs: <Source>[
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <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'),
|
||||
@ -40,7 +29,29 @@ const Target unpackLinux = Target(
|
||||
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,
|
||||
);
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => <Target>[];
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, Environment environment) async {
|
||||
final String basePath = artifacts.getArtifactPath(Artifact.linuxDesktopPath);
|
||||
for (File input in inputFiles) {
|
||||
if (fs.path.basename(input.path) == 'linux.dart') {
|
||||
continue;
|
||||
}
|
||||
final String outputPath = fs.path.join(
|
||||
environment.projectDir.path,
|
||||
'linux',
|
||||
'flutter',
|
||||
fs.path.relative(input.path, from: basePath),
|
||||
);
|
||||
final File destinationFile = fs.file(outputPath);
|
||||
if (!destinationFile.parent.existsSync()) {
|
||||
destinationFile.parent.createSync(recursive: true);
|
||||
}
|
||||
fs.file(input).copySync(destinationFile.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import '../../base/io.dart';
|
||||
import '../../base/process_manager.dart';
|
||||
import '../../globals.dart';
|
||||
import '../build_system.dart';
|
||||
import 'assets.dart';
|
||||
import 'dart.dart';
|
||||
|
||||
const String _kOutputPrefix = '{PROJECT_DIR}/macos/Flutter/FlutterMacOS.framework';
|
||||
|
||||
/// Copy the macOS framework to the correct copy dir by invoking 'cp -R'.
|
||||
///
|
||||
@ -19,37 +19,20 @@ import 'dart.dart';
|
||||
/// 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);
|
||||
}
|
||||
class UnpackMacOS extends Target {
|
||||
const UnpackMacOS();
|
||||
|
||||
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}',
|
||||
);
|
||||
}
|
||||
}
|
||||
@override
|
||||
String get name => 'unpack_macos';
|
||||
|
||||
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>[
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
|
||||
Source.artifact(Artifact.flutterMacOSFramework),
|
||||
],
|
||||
outputs: <Source>[
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('$_kOutputPrefix/FlutterMacOS'),
|
||||
// Headers
|
||||
Source.pattern('$_kOutputPrefix/Headers/FLEOpenGLContextHandling.h'),
|
||||
@ -68,33 +51,30 @@ const Target unpackMacos = Target(
|
||||
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,
|
||||
]
|
||||
);
|
||||
@override
|
||||
List<Target> get dependencies => <Target>[];
|
||||
|
||||
/// Build a macOS release application.
|
||||
const Target macoReleaseApplication = Target(
|
||||
name: 'release_macos_application',
|
||||
buildAction: null,
|
||||
inputs: <Source>[],
|
||||
outputs: <Source>[],
|
||||
dependencies: <Target>[
|
||||
unpackMacos,
|
||||
aotElfRelease,
|
||||
copyAssets,
|
||||
]
|
||||
);
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, 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 = await processManager
|
||||
.run(<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}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,33 +7,21 @@ 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>[
|
||||
class UnpackWindows extends Target {
|
||||
const UnpackWindows();
|
||||
|
||||
@override
|
||||
String get name => 'unpack_windows';
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[
|
||||
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/windows.dart'),
|
||||
Source.artifact(Artifact.windowsDesktopPath),
|
||||
],
|
||||
outputs: <Source>[
|
||||
];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <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'),
|
||||
@ -44,7 +32,30 @@ const Target unpackWindows = Target(
|
||||
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,
|
||||
);
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => const <Target>[];
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, Environment environment) async {
|
||||
// This path needs to match the prefix in the rule below.
|
||||
final String basePath = artifacts.getArtifactPath(Artifact.windowsDesktopPath);
|
||||
for (File input in inputFiles) {
|
||||
if (fs.path.basename(input.path) == 'windows.dart') {
|
||||
continue;
|
||||
}
|
||||
final String outputPath = fs.path.join(
|
||||
environment.projectDir.path,
|
||||
'windows',
|
||||
'flutter',
|
||||
fs.path.relative(input.path, from: basePath),
|
||||
);
|
||||
final File destinationFile = fs.file(outputPath);
|
||||
if (!destinationFile.parent.existsSync()) {
|
||||
destinationFile.parent.createSync(recursive: true);
|
||||
}
|
||||
fs.file(input).copySync(destinationFile.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,13 @@
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../build_info.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import '../convert.dart';
|
||||
import '../build_system/targets/assets.dart';
|
||||
import '../build_system/targets/dart.dart';
|
||||
import '../build_system/targets/ios.dart';
|
||||
import '../build_system/targets/linux.dart';
|
||||
import '../build_system/targets/macos.dart';
|
||||
import '../build_system/targets/windows.dart';
|
||||
import '../globals.dart';
|
||||
import '../project.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
@ -14,30 +18,23 @@ import '../runner/flutter_command.dart';
|
||||
/// The [BuildSystem] instance.
|
||||
BuildSystem get buildSystem => context.get<BuildSystem>();
|
||||
|
||||
/// All currently implemented targets.
|
||||
const List<Target> _kDefaultTargets = <Target>[
|
||||
UnpackMacOS(),
|
||||
UnpackLinux(),
|
||||
UnpackWindows(),
|
||||
CopyAssets(),
|
||||
KernelSnapshot(),
|
||||
AotElfProfile(),
|
||||
AotElfRelease(),
|
||||
AotAssemblyProfile(),
|
||||
AotAssemblyRelease(),
|
||||
];
|
||||
|
||||
/// 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
|
||||
Future<FlutterCommandResult> runCommand() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AssembleBase extends FlutterCommand {
|
||||
AssembleBase() {
|
||||
argParser.addMultiOption(
|
||||
'define',
|
||||
abbr: 'd',
|
||||
@ -57,36 +54,19 @@ abstract class AssembleBase extends FlutterCommand {
|
||||
);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
@override
|
||||
String get description => 'Assemble and build flutter resources.';
|
||||
|
||||
/// 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);
|
||||
}
|
||||
@override
|
||||
String get name => 'assemble';
|
||||
|
||||
/// The name of the target we are describing or building.
|
||||
String get targetName {
|
||||
/// The target we are building.
|
||||
Target get target {
|
||||
if (argResults.rest.isEmpty) {
|
||||
throwToolExit('missing target name for flutter assemble.');
|
||||
}
|
||||
return argResults.rest.first;
|
||||
final String name = argResults.rest.first;
|
||||
return _kDefaultTargets.firstWhere((Target target) => target.name == name);
|
||||
}
|
||||
|
||||
/// The environmental configuration for a build invocation.
|
||||
@ -115,19 +95,10 @@ abstract class AssembleBase extends FlutterCommand {
|
||||
}
|
||||
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
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final BuildResult result = await buildSystem.build(targetName, environment, BuildSystemConfig(
|
||||
final BuildResult result = await buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig(
|
||||
resourcePoolSize: argResults['resource-pool-size'],
|
||||
));
|
||||
if (!result.success) {
|
||||
@ -142,67 +113,3 @@ class AssembleRun extends AssembleBase {
|
||||
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
|
||||
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
|
||||
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
|
||||
Future<FlutterCommandResult> runCommand() {
|
||||
printStatus(environment.buildDir.path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ Future<T> runInContext<T>(
|
||||
Artifacts: () => CachedArtifacts(),
|
||||
AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
|
||||
BotDetector: () => const BotDetector(),
|
||||
BuildSystem: () => BuildSystem(),
|
||||
BuildSystem: () => const BuildSystem(),
|
||||
Cache: () => Cache(),
|
||||
ChromeLauncher: () => const ChromeLauncher(),
|
||||
CocoaPods: () => CocoaPods(),
|
||||
|
@ -2,14 +2,10 @@
|
||||
// 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';
|
||||
@ -23,83 +19,85 @@ void main() {
|
||||
Cache.disableLocking();
|
||||
});
|
||||
|
||||
group(Target, () {
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Testbed testbed;
|
||||
MockPlatform mockPlatform;
|
||||
Environment environment;
|
||||
Target fooTarget;
|
||||
Target barTarget;
|
||||
Target fizzTarget;
|
||||
BuildSystem buildSystem;
|
||||
Target sharedTarget;
|
||||
int fooInvocations;
|
||||
int barInvocations;
|
||||
int shared;
|
||||
|
||||
setUp(() {
|
||||
fooInvocations = 0;
|
||||
barInvocations = 0;
|
||||
shared = 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) {
|
||||
|
||||
/// Create various testing targets.
|
||||
fooTarget = TestTarget((List<File> inputFiles, Environment environment) async {
|
||||
environment
|
||||
.buildDir
|
||||
.childFile('out')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('hey');
|
||||
fooInvocations++;
|
||||
}
|
||||
);
|
||||
barTarget = Target(
|
||||
name: 'bar',
|
||||
inputs: const <Source>[
|
||||
})
|
||||
..name = 'foo'
|
||||
..inputs = const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
]
|
||||
..outputs = 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) {
|
||||
]
|
||||
..dependencies = <Target>[];
|
||||
barTarget = TestTarget((List<File> inputFiles, Environment environment) async {
|
||||
environment.buildDir
|
||||
.childFile('bar')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('there');
|
||||
barInvocations++;
|
||||
}
|
||||
);
|
||||
fizzTarget = Target(
|
||||
name: 'fizz',
|
||||
inputs: const <Source>[
|
||||
})
|
||||
..name = 'bar'
|
||||
..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) {
|
||||
]
|
||||
..outputs = const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/bar'),
|
||||
]
|
||||
..dependencies = <Target>[];
|
||||
fizzTarget = TestTarget((List<File> inputFiles, Environment environment) async {
|
||||
throw Exception('something bad happens');
|
||||
}
|
||||
})
|
||||
..name = 'fizz'
|
||||
..inputs = const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/out'),
|
||||
]
|
||||
..outputs = const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/fizz'),
|
||||
]
|
||||
..dependencies = <Target>[fooTarget];
|
||||
sharedTarget = TestTarget((List<File> inputFiles, Environment environment) async {
|
||||
shared += 1;
|
||||
})
|
||||
..name = 'shared'
|
||||
..inputs = const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
];
|
||||
testbed = Testbed(
|
||||
setup: () {
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
);
|
||||
buildSystem = BuildSystem(<String, Target>{
|
||||
fooTarget.name: fooTarget,
|
||||
barTarget.name: barTarget,
|
||||
fizzTarget.name: fizzTarget,
|
||||
});
|
||||
fs.file('foo.dart')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('');
|
||||
fs.file('pubspec.yaml').createSync();
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
Platform: () => mockPlatform,
|
||||
@ -107,53 +105,31 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
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());
|
||||
final BuildResult buildResult = await buildSystem.build(fooTarget, environment);
|
||||
|
||||
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>[
|
||||
final Target badTarget = TestTarget((List<File> inputFiles, Environment environment) async {})
|
||||
..inputs = const <Source>[
|
||||
Source.pattern('{PROJECT_DIR}/foo.dart'),
|
||||
],
|
||||
outputs: const <Source>[
|
||||
]
|
||||
..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());
|
||||
];
|
||||
final BuildResult result = await buildSystem.build(badTarget, environment);
|
||||
|
||||
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());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
final File stampFile = fs.file(fs.path.join(environment.buildDir.path, 'foo.stamp'));
|
||||
expect(stampFile.existsSync(), true);
|
||||
@ -163,59 +139,72 @@ void main() {
|
||||
}));
|
||||
|
||||
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());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
test('Re-invoke build if input is modified', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
fs.file('foo.dart').writeAsStringSync('new contents');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
expect(fooInvocations, 2);
|
||||
}));
|
||||
|
||||
test('does not re-invoke build if input timestamp changes', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
fs.file('foo.dart').writeAsStringSync('');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
test('does not re-invoke build if output timestamp changes', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
environment.buildDir.childFile('out').writeAsStringSync('hey');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
expect(fooInvocations, 1);
|
||||
}));
|
||||
|
||||
|
||||
test('Re-invoke build if output is modified', () => testbed.run(() async {
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
|
||||
environment.buildDir.childFile('out').writeAsStringSync('Something different');
|
||||
|
||||
await buildSystem.build('foo', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(fooTarget, environment);
|
||||
expect(fooInvocations, 2);
|
||||
}));
|
||||
|
||||
test('Runs dependencies of targets', () => testbed.run(() async {
|
||||
await buildSystem.build('bar', environment, const BuildSystemConfig());
|
||||
barTarget.dependencies.add(fooTarget);
|
||||
|
||||
await buildSystem.build(barTarget, environment);
|
||||
|
||||
expect(fs.file(fs.path.join(environment.buildDir.path, 'bar')).existsSync(), true);
|
||||
expect(fooInvocations, 1);
|
||||
expect(barInvocations, 1);
|
||||
}));
|
||||
|
||||
test('Only invokes shared dependencies once', () => testbed.run(() async {
|
||||
fooTarget.dependencies.add(sharedTarget);
|
||||
barTarget.dependencies.add(sharedTarget);
|
||||
barTarget.dependencies.add(fooTarget);
|
||||
|
||||
await buildSystem.build(barTarget, environment);
|
||||
|
||||
expect(shared, 1);
|
||||
}));
|
||||
|
||||
|
||||
test('handles a throwing build action', () => testbed.run(() async {
|
||||
final BuildResult result = await buildSystem.build('fizz', environment, const BuildSystemConfig());
|
||||
final BuildResult result = await buildSystem.build(fizzTarget, environment);
|
||||
|
||||
expect(result.hasException, true);
|
||||
}));
|
||||
@ -235,303 +224,12 @@ void main() {
|
||||
});
|
||||
}));
|
||||
|
||||
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>[])
|
||||
);
|
||||
final Target barTarget = TestTarget()..name = 'bar';
|
||||
final Target fooTarget = TestTarget()..name = 'foo';
|
||||
barTarget.dependencies.add(fooTarget);
|
||||
fooTarget.dependencies.add(barTarget);
|
||||
|
||||
expect(() => checkCycles(barTarget), throwsA(isInstanceOf<CycleException>()));
|
||||
});
|
||||
}
|
||||
@ -541,14 +239,23 @@ 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;
|
||||
}
|
||||
class TestTarget extends Target {
|
||||
TestTarget([this._build]);
|
||||
|
||||
final Future<void> Function(List<File> inputFiles, Environment environment) _build;
|
||||
|
||||
@override
|
||||
List<File> outputs(Environment environment) {
|
||||
return null;
|
||||
}
|
||||
Future<void> build(List<File> inputFiles, Environment environment) => _build(inputFiles, environment);
|
||||
|
||||
@override
|
||||
List<Target> dependencies = <Target>[];
|
||||
|
||||
@override
|
||||
List<Source> inputs = <Source>[];
|
||||
|
||||
@override
|
||||
String name = 'test';
|
||||
|
||||
@override
|
||||
List<Source> outputs = <Source>[];
|
||||
}
|
||||
|
@ -12,19 +12,9 @@ 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 CycleException cycleException = CycleException(<Target>{
|
||||
TestTarget()..name = 'foo',
|
||||
TestTarget()..name = 'bar',
|
||||
});
|
||||
final InvalidPatternException invalidPatternException = InvalidPatternException(
|
||||
'ABC'
|
||||
@ -70,3 +60,24 @@ void main() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class TestTarget extends Target {
|
||||
TestTarget([this._build]);
|
||||
|
||||
final Future<void> Function(List<File> inputFiles, Environment environment) _build;
|
||||
|
||||
@override
|
||||
Future<void> build(List<File> inputFiles, Environment environment) => _build(inputFiles, environment);
|
||||
|
||||
@override
|
||||
List<Target> dependencies = <Target>[];
|
||||
|
||||
@override
|
||||
List<Source> inputs = <Source>[];
|
||||
|
||||
@override
|
||||
String name = 'test';
|
||||
|
||||
@override
|
||||
List<Source> outputs = <Source>[];
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
// 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/file_hash_store.dart';
|
||||
import 'package:flutter_tools/src/build_system/filecache.pb.dart' as pb;
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
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());
|
||||
}));
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
// 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/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/source.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
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 {FLUTTER_ROOT}/foo', () => testbed.run(() {
|
||||
final String path = fs.path.join(environment.flutterRootDir.path, 'foo');
|
||||
fs.file(path).createSync();
|
||||
const Source barSource = Source.pattern('{FLUTTER_ROOT}/foo');
|
||||
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>()));
|
||||
}));
|
||||
}
|
||||
|
||||
class TestBehavior extends SourceBehavior {
|
||||
@override
|
||||
List<File> inputs(Environment environment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<File> outputs(Environment environment) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -10,19 +10,18 @@ import '../../../src/common.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('copy_assets', () {
|
||||
Testbed testbed;
|
||||
BuildSystem buildSystem;
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Environment environment;
|
||||
Testbed testbed;
|
||||
|
||||
setUp(() {
|
||||
testbed = Testbed(setup: () {
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
);
|
||||
buildSystem = BuildSystem(<String, Target>{
|
||||
copyAssets.name: copyAssets,
|
||||
});
|
||||
fs.file(fs.path.join('packages', 'flutter_tools', 'lib', 'src',
|
||||
'build_system', 'targets', 'assets.dart'))
|
||||
..createSync(recursive: true);
|
||||
fs.file(fs.path.join('assets', 'foo', 'bar.png'))
|
||||
..createSync(recursive: true);
|
||||
fs.file('.packages')
|
||||
@ -40,7 +39,7 @@ flutter:
|
||||
});
|
||||
|
||||
test('Copies files to correct asset directory', () => testbed.run(() async {
|
||||
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const CopyAssets(), environment);
|
||||
|
||||
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);
|
||||
@ -50,7 +49,7 @@ flutter:
|
||||
}));
|
||||
|
||||
test('Does not leave stale files in build directory', () => testbed.run(() async {
|
||||
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const CopyAssets(), environment);
|
||||
|
||||
expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true);
|
||||
// Modify manifest to remove asset.
|
||||
@ -61,10 +60,9 @@ name: example
|
||||
|
||||
flutter:
|
||||
''');
|
||||
await buildSystem.build('copy_assets', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const CopyAssets(), environment);
|
||||
|
||||
// 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);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ 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/build_system/targets/ios.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
@ -22,9 +23,8 @@ import '../../../src/mocks.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('dart rules', () {
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Testbed testbed;
|
||||
BuildSystem buildSystem;
|
||||
Environment androidEnvironment;
|
||||
Environment iosEnvironment;
|
||||
MockProcessManager mockProcessManager;
|
||||
@ -35,8 +35,8 @@ void main() {
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockXcode = MockXcode();
|
||||
mockProcessManager = MockProcessManager();
|
||||
testbed = Testbed(setup: () {
|
||||
androidEnvironment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
@ -52,7 +52,6 @@ void main() {
|
||||
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
|
||||
}
|
||||
);
|
||||
buildSystem = BuildSystem();
|
||||
HostPlatform hostPlatform;
|
||||
if (platform.isWindows) {
|
||||
hostPlatform = HostPlatform.windows_x64;
|
||||
@ -90,6 +89,8 @@ flutter_tools:lib/''');
|
||||
fs.path.join('lib', 'foo.dart'),
|
||||
fs.path.join('lib', 'bar.dart'),
|
||||
fs.path.join('lib', 'fizz'),
|
||||
fs.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'dart.dart'),
|
||||
fs.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'ios.dart'),
|
||||
];
|
||||
for (String path in paths) {
|
||||
fs.file(path).createSync(recursive: true);
|
||||
@ -101,62 +102,79 @@ flutter_tools:lib/''');
|
||||
});
|
||||
|
||||
test('kernel_snapshot Produces correct output directory', () => testbed.run(() async {
|
||||
await buildSystem.build('kernel_snapshot', androidEnvironment, const BuildSystemConfig());
|
||||
await buildSystem.build(const KernelSnapshot(), androidEnvironment);
|
||||
|
||||
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path,'main.app.dill')).existsSync(), true);
|
||||
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path,'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());
|
||||
final BuildResult result = await buildSystem.build(const KernelSnapshot(),
|
||||
androidEnvironment..defines.remove(kBuildMode));
|
||||
|
||||
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());
|
||||
await buildSystem.build(const AotElfProfile(), androidEnvironment);
|
||||
|
||||
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'main.app.dill')).existsSync(), true);
|
||||
expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, '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());
|
||||
final BuildResult result = await buildSystem.build(const AotElfProfile(),
|
||||
androidEnvironment..defines.remove(kBuildMode));
|
||||
|
||||
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());
|
||||
final BuildResult result = await buildSystem.build(const AotElfProfile(),
|
||||
androidEnvironment..defines.remove(kTargetPlatform));
|
||||
|
||||
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());
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(),
|
||||
iosEnvironment..defines.remove(kBuildMode));
|
||||
|
||||
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());
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(),
|
||||
iosEnvironment..defines.remove(kTargetPlatform));
|
||||
|
||||
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());
|
||||
final BuildResult result = await buildSystem
|
||||
.build(const AotAssemblyProfile(), androidEnvironment);
|
||||
|
||||
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(const AotAssemblyProfile(), iosEnvironment);
|
||||
expect(result.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
}));
|
||||
|
||||
test('aot_assembly_profile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async {
|
||||
iosEnvironment.defines[kIosArchs] = 'arm64';
|
||||
iosEnvironment.defines[kBitcodeFlag] = 'true';
|
||||
@ -176,8 +194,7 @@ flutter_tools:lib/''');
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.dsymutil(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
|
||||
final BuildResult result = await buildSystem.build('aot_assembly_profile',
|
||||
iosEnvironment, const BuildSystemConfig());
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
|
||||
|
||||
expect(result.success, true);
|
||||
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1);
|
||||
@ -207,8 +224,7 @@ flutter_tools:lib/''');
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.dsymutil(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
|
||||
final BuildResult result = await buildSystem.build('aot_assembly_profile',
|
||||
iosEnvironment, const BuildSystemConfig());
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
|
||||
|
||||
expect(result.success, true);
|
||||
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2);
|
||||
@ -229,14 +245,12 @@ flutter_tools:lib/''');
|
||||
stderr: '',
|
||||
);
|
||||
});
|
||||
final BuildResult result = await buildSystem.build('aot_assembly_profile',
|
||||
iosEnvironment, const BuildSystemConfig());
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
|
||||
|
||||
expect(result.success, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
|
@ -13,14 +13,14 @@ import '../../../src/common.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('unpack_linux', () {
|
||||
Testbed testbed;
|
||||
BuildSystem buildSystem;
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Environment environment;
|
||||
MockPlatform mockPlatform;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
Cache.flutterRoot = '';
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@ -33,9 +33,6 @@ void main() {
|
||||
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();
|
||||
@ -43,6 +40,7 @@ void main() {
|
||||
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.file('packages/flutter_tools/lib/src/build_system/targets/linux.dart').createSync(recursive: true);
|
||||
fs.directory('linux').createSync();
|
||||
}, overrides: <Type, Generator>{
|
||||
Platform: () => mockPlatform,
|
||||
@ -50,7 +48,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Copies files to correct cache directory', () => testbed.run(() async {
|
||||
final BuildResult result = await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
|
||||
final BuildResult result = await buildSystem.build(const UnpackLinux(), environment);
|
||||
|
||||
expect(result.hasException, false);
|
||||
expect(fs.file('linux/flutter/libflutter_linux.so').existsSync(), true);
|
||||
@ -63,22 +61,24 @@ void main() {
|
||||
}));
|
||||
|
||||
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());
|
||||
await buildSystem.build(const UnpackLinux(), environment);
|
||||
// Set a date in the far distant past to deal with the limited resolution
|
||||
// of the windows filesystem.
|
||||
final DateTime theDistantPast = DateTime(1991, 8, 23);
|
||||
fs.file('linux/flutter/libflutter_linux.so').setLastModifiedSync(theDistantPast);
|
||||
await buildSystem.build(const UnpackLinux(), environment);
|
||||
|
||||
expect(fs.file('linux/flutter/libflutter_linux.so').statSync().modified, equals(modified));
|
||||
expect(fs.file('linux/flutter/libflutter_linux.so').statSync().modified, equals(theDistantPast));
|
||||
}));
|
||||
|
||||
test('Detects changes in input cache files', () => testbed.run(() async {
|
||||
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const UnpackLinux(), environment);
|
||||
fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').writeAsStringSync('asd'); // modify cache.
|
||||
|
||||
await buildSystem.build('unpack_linux', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const UnpackLinux(), environment);
|
||||
|
||||
expect(fs.file('linux/flutter/libflutter_linux.so').readAsStringSync(), 'asd');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
class MockPlatform extends Mock implements Platform {}
|
||||
|
@ -8,6 +8,7 @@ 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:flutter_tools/src/cache.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
@ -15,12 +16,16 @@ import '../../../src/common.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('unpack_macos', () {
|
||||
Testbed testbed;
|
||||
BuildSystem buildSystem;
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Environment environment;
|
||||
MockPlatform mockPlatform;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
Cache.flutterRoot = '';
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockPlatform = MockPlatform();
|
||||
when(mockPlatform.isWindows).thenReturn(false);
|
||||
@ -30,9 +35,6 @@ void main() {
|
||||
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'),
|
||||
@ -48,11 +50,12 @@ void main() {
|
||||
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'),
|
||||
fs.file('packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
|
||||
];
|
||||
for (File input in inputs) {
|
||||
input.createSync(recursive: true);
|
||||
}
|
||||
when(processManager.runSync(any)).thenAnswer((Invocation invocation) {
|
||||
when(processManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
final List<String> arguments = invocation.positionalArguments.first;
|
||||
final Directory source = fs.directory(arguments[arguments.length - 2]);
|
||||
final Directory target = fs.directory(arguments.last)
|
||||
@ -76,7 +79,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Copies files to correct cache directory', () => testbed.run(() async {
|
||||
await buildSystem.build('unpack_macos', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const UnpackMacOS(), environment);
|
||||
|
||||
expect(fs.directory('macos/Flutter/FlutterMacOS.framework').existsSync(), true);
|
||||
expect(fs.file('macos/Flutter/FlutterMacOS.framework/FlutterMacOS').existsSync(), true);
|
||||
@ -94,7 +97,6 @@ void main() {
|
||||
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 {}
|
||||
|
@ -14,18 +14,17 @@ import '../../../src/common.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('unpack_windows', () {
|
||||
Testbed testbed;
|
||||
BuildSystem buildSystem;
|
||||
const BuildSystem buildSystem = BuildSystem();
|
||||
Environment environment;
|
||||
Platform platform;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
Cache.flutterRoot = '';
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
Cache.flutterRoot = '';
|
||||
platform = MockPlatform();
|
||||
when(platform.isWindows).thenReturn(true);
|
||||
when(platform.isMacOS).thenReturn(false);
|
||||
@ -35,9 +34,6 @@ void main() {
|
||||
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();
|
||||
@ -50,6 +46,7 @@ void main() {
|
||||
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.file(r'C:\packages\flutter_tools\lib\src\build_system\targets\windows.dart').createSync(recursive: true);
|
||||
fs.directory('windows').createSync();
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows),
|
||||
@ -58,7 +55,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('Copies files to correct cache directory', () => testbed.run(() async {
|
||||
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const UnpackWindows(), environment);
|
||||
|
||||
expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true);
|
||||
expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true);
|
||||
@ -75,23 +72,29 @@ void main() {
|
||||
}));
|
||||
|
||||
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());
|
||||
await buildSystem.build(const UnpackWindows(), environment);
|
||||
// Set a date in the far distant past to deal with the limited resolution
|
||||
// of the windows filesystem.
|
||||
final DateTime theDistantPast = DateTime(1991, 8, 23);
|
||||
fs.file(r'C:\windows\flutter\flutter_export.h').setLastModifiedSync(theDistantPast);
|
||||
await buildSystem.build(const UnpackWindows(), environment);
|
||||
|
||||
expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, equals(modified));
|
||||
expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, equals(theDistantPast));
|
||||
}));
|
||||
|
||||
test('Detects changes in input cache files', () => testbed.run(() async {
|
||||
await buildSystem.build('unpack_windows', environment, const BuildSystemConfig());
|
||||
await buildSystem.build(const UnpackWindows(), environment);
|
||||
// Set a date in the far distant past to deal with the limited resolution
|
||||
// of the windows filesystem.
|
||||
final DateTime theDistantPast = DateTime(1991, 8, 23);
|
||||
fs.file(r'C:\windows\flutter\flutter_export.h').setLastModifiedSync(theDistantPast);
|
||||
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());
|
||||
await buildSystem.build(const UnpackWindows(), environment);
|
||||
|
||||
expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, isNot(modified));
|
||||
}), skip: true); // TODO(jonahwilliams): track down flakiness.
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
class MockPlatform extends Mock implements Platform {}
|
||||
|
@ -3,7 +3,6 @@
|
||||
// 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_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
@ -15,7 +14,6 @@ import '../../src/common.dart';
|
||||
import '../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
group('Assemble', () {
|
||||
Testbed testbed;
|
||||
MockBuildSystem mockBuildSystem;
|
||||
|
||||
@ -30,53 +28,17 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
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(fs.path.join('.dart_tool', 'flutter_build')).absolute,
|
||||
);
|
||||
|
||||
expect(bufferLogger.statusText.trim(), environment.buildDir.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 {
|
||||
when(mockBuildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
|
||||
.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']);
|
||||
await commandRunner.run(<String>['assemble', 'unpack_macos']);
|
||||
final BufferLogger bufferLogger = logger;
|
||||
|
||||
expect(bufferLogger.statusText.trim(), 'build succeeded');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
class MockBuildSystem extends Mock implements BuildSystem {}
|
||||
|
Loading…
Reference in New Issue
Block a user