From 188093c912bc56c72832b3997aef8821164a93b7 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 25 Jul 2019 08:50:03 -0700 Subject: [PATCH] Rearrange flutter assemble implementation (#36240) --- .../lib/src/build_system/build_system.dart | 169 ++-- .../lib/src/build_system/source.dart | 3 +- .../lib/src/build_system/targets/assets.dart | 95 +-- .../lib/src/build_system/targets/dart.dart | 328 +++----- .../lib/src/build_system/targets/ios.dart | 175 ++++- .../lib/src/build_system/targets/linux.dart | 67 +- .../lib/src/build_system/targets/macos.dart | 96 +-- .../lib/src/build_system/targets/windows.dart | 69 +- .../lib/src/commands/assemble.dart | 149 +--- .../flutter_tools/lib/src/context_runner.dart | 2 +- .../build_system/build_system_test.dart | 723 ++++++------------ .../build_system/exceptions_test.dart | 37 +- .../build_system/filecache_test.dart | 68 ++ .../build_system/source_test.dart | 150 ++++ .../build_system/targets/assets_test.dart | 94 ++- .../build_system/targets/dart_test.dart | 404 +++++----- .../build_system/targets/linux_test.dart | 128 ++-- .../build_system/targets/macos_test.dart | 158 ++-- .../build_system/targets/windows_test.dart | 155 ++-- .../general.shard/commands/assemble_test.dart | 84 +- 20 files changed, 1458 insertions(+), 1696 deletions(-) create mode 100644 packages/flutter_tools/test/general.shard/build_system/filecache_test.dart create mode 100644 packages/flutter_tools/test/general.shard/build_system/source_test.dart diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart index c0f9d224e2e..5d0a88b8658 100644 --- a/packages/flutter_tools/lib/src/build_system/build_system.dart +++ b/packages/flutter_tools/lib/src/build_system/build_system.dart @@ -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 Function( - Map 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 [], - }); - +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 dependencies; + List get dependencies; /// The input [Source]s which are diffed to determine if a target should run. - final List inputs; + List get inputs; /// The output [Source]s which we attempt to verify are correctly produced. - final List outputs; + List get outputs; /// The action which performs this build step. - final BuildAction buildAction; + Future build(List inputFiles, Environment environment); /// Collect hashes for all inputs to determine if any have changed. - Future> computeChanges( + /// + /// Returns whether this target can be skipped. + Future computeChanges( List inputs, Environment environment, FileHashStore fileHashStore, ) async { - final Map updates = {}; final File stamp = _findStampFile(environment); final Set previousInputs = {}; final List previousOutputs = []; + 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 values = json.decode(content); final List inputs = values['inputs']; @@ -168,12 +144,13 @@ class Target { inputs.cast().forEach(previousInputs.add); outputs.cast().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 sourcesToHash = []; final List missingInputs = []; 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 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 currentInputPaths = Set.from( - inputs.map((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 targets]) - : targets = targets ?? _defaultTargets; + const BuildSystem(); - /// All currently registered targets. - static final Map _defaultTargets = { - 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 targets; - - /// Build the target `name` and all of its dependencies. + /// Build `target` and all of its dependencies. Future 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> 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> fold(Map> accumulation, Target current) { - accumulation[current.name] = current.toJson(environment); - return accumulation; - } - - final Map> result = - >{}; - final Map> 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 inputs = target.resolveInputs(environment); - final Map 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 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; diff --git a/packages/flutter_tools/lib/src/build_system/source.dart b/packages/flutter_tools/lib/src/build_system/source.dart index e9f1e1642cb..a9727e808f8 100644 --- a/packages/flutter_tools/lib/src/build_system/source.dart +++ b/packages/flutter_tools/lib/src/build_system/source.dart @@ -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); diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index e5545db43df..7b21df8f524 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -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 results = []; final Iterable files = assetBundle.entries.values.whereType(); for (DevFSFileContent devFsContent in files) { @@ -40,56 +38,65 @@ class AssetBehavior extends SourceBehavior { packagesPath: environment.projectDir.childFile('.packages').path, ); final List results = []; - for (MapEntry 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 copyAssetsInvocation(Map updates, Environment environment) async { - final Directory output = environment - .buildDir - .childDirectory('flutter_assets'); - if (output.existsSync()) { - output.deleteSync(recursive: true); - } - output.createSync(recursive: true); - final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); - await assetBundle.build( - manifestPath: environment.projectDir.childFile('pubspec.yaml').path, - packagesPath: environment.projectDir.childFile('.packages').path, - ); - // Limit number of open files to avoid running out of file descriptors. - final Pool pool = Pool(64); - await Future.wait( - assetBundle.entries.entries.map>((MapEntry entry) async { - final PoolResource resource = await pool.request(); - try { - final File file = fs.file(fs.path.join(output.path, entry.key)); - file.parent.createSync(recursive: true); - await file.writeAsBytes(await entry.value.contentsAsBytes()); - } finally { - resource.release(); - } - })); -} +/// Copy the assets defined in the flutter manifest into a build directory. +class CopyAssets extends Target { + const CopyAssets(); -/// Copy the assets used in the application into a build directory. -const Target copyAssets = Target( - name: 'copy_assets', - inputs: [ + @override + String get name => 'copy_assets'; + + @override + List get dependencies => const []; + + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'), Source.pattern('{PROJECT_DIR}/pubspec.yaml'), Source.behavior(AssetBehavior()), - ], - outputs: [ + ]; + + @override + List get outputs => const [ 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: [], - buildAction: copyAssetsInvocation, -); + ]; + + @override + Future build(List inputFiles, Environment environment) async { + final Directory output = environment + .buildDir + .childDirectory('flutter_assets'); + if (output.existsSync()) { + output.deleteSync(recursive: true); + } + output.createSync(recursive: true); + final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); + await assetBundle.build( + manifestPath: environment.projectDir.childFile('pubspec.yaml').path, + packagesPath: environment.projectDir.childFile('.packages').path, + ); + // Limit number of open files to avoid running out of file descriptors. + final Pool pool = Pool(64); + await Future.wait( + assetBundle.entries.entries.map>((MapEntry entry) async { + final PoolResource resource = await pool.request(); + try { + final File file = fs.file(fs.path.join(output.path, entry.key)); + file.parent.createSync(recursive: true); + await file.writeAsBytes(await entry.value.contentsAsBytes()); + } finally { + resource.release(); + } + })); + } +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart index d704ad3ff51..6b7be2e4f97 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart @@ -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 compileKernel(Map 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 compileAotElf(Map 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 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 compileAotAssembly(Map 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 iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList() - ?? [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> pending = >[]; - 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 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([ - '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: [ +class KernelSnapshot extends Target { + const KernelSnapshot(); + + @override + String get name => 'kernel_snapshot'; + + @override + List get inputs => const [ + 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.pattern('{BUILD_DIR}/main.app.dill'), - ], - dependencies: [], - buildAction: compileKernel, -); + ]; + + @override + List get outputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + ]; + + @override + List get dependencies => []; + + @override + Future build(List 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 build(List 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.pattern('{BUILD_DIR}/main.app.dill'), +class AotElfProfile extends AotElfBase { + const AotElfProfile(); + + @override + String get name => 'aot_elf_profile'; + + @override + List get inputs => const [ + 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: [ + ]; + + @override + List get outputs => const [ Source.pattern('{BUILD_DIR}/app.so'), - ], - dependencies: [ - kernelSnapshot, - ], - buildAction: compileAotElf, -); + ]; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} /// Generate an ELF binary from a dart kernel file in release mode. -const Target aotElfRelease= Target( - name: 'aot_elf_release', - inputs: [ - Source.pattern('{BUILD_DIR}/main.app.dill'), +class AotElfRelease extends AotElfBase { + const AotElfRelease(); + + @override + String get name => 'aot_elf_release'; + + @override + List get inputs => const [ + 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: [ + ]; + + @override + List get outputs => const [ Source.pattern('{BUILD_DIR}/app.so'), - ], - dependencies: [ - 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.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: [ - // 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: [ - 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.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: [ - // 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: [ - kernelSnapshot, - ], - buildAction: compileAotAssembly, -); + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 19e38ea484b..f0ca9a640d4 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -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: [], - outputs: [], - dependencies: [ - 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: [], - outputs: [], - dependencies: [ - copyAssets, - aotAssemblyProfile, - ] -); + @override + Future build(List 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 iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList() + ?? [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: [], - outputs: [], - dependencies: [ - 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> pending = >[]; + 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 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([ + '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 get inputs => const [ + 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 get outputs => const [ + Source.pattern('{BUILD_DIR}/App.framework/App'), + ]; + + @override + List get dependencies => const [ + 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 get inputs => const [ + 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 get outputs => const [ + Source.pattern('{BUILD_DIR}/App.framework/App'), + ]; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart index c5ad64d1389..d877cd49824 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -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 copyLinuxAssets(Map 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: [ +class UnpackLinux extends Target { + const UnpackLinux(); + + @override + String get name => 'unpack_linux'; + + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/linux.dart'), Source.artifact(Artifact.linuxDesktopPath), - ], - outputs: [ + ]; + + @override + List get outputs => const [ 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: [], - buildAction: copyLinuxAssets, -); + ]; + + @override + List get dependencies => []; + + @override + Future build(List 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); + } + } +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart index 7bfb9239f7e..3d14ca7a7ce 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -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 copyFramework(Map 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(['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: [ + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'), Source.artifact(Artifact.flutterMacOSFramework), - ], - outputs: [ + ]; + + @override + List get outputs => const [ 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: [], - buildAction: copyFramework, -); + ]; -/// Build a macOS application. -const Target macosApplication = Target( - name: 'debug_macos_application', - buildAction: null, - inputs: [], - outputs: [], - dependencies: [ - unpackMacos, - kernelSnapshot, - copyAssets, - ] -); + @override + List get dependencies => []; -/// Build a macOS release application. -const Target macoReleaseApplication = Target( - name: 'release_macos_application', - buildAction: null, - inputs: [], - outputs: [], - dependencies: [ - unpackMacos, - aotElfRelease, - copyAssets, - ] -); + @override + Future build(List 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(['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}', + ); + } + } +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/windows.dart b/packages/flutter_tools/lib/src/build_system/targets/windows.dart index 2677cba6983..b2e9dbfd627 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -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 copyWindowsAssets(Map 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: [ +class UnpackWindows extends Target { + const UnpackWindows(); + + @override + String get name => 'unpack_windows'; + + @override + List get inputs => const [ + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/windows.dart'), Source.artifact(Artifact.windowsDesktopPath), - ], - outputs: [ + ]; + + @override + List get outputs => const [ 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: [], - buildAction: copyWindowsAssets, -); + ]; + + @override + List get dependencies => const []; + + @override + Future build(List 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); + } + } +} diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 192e7421c57..78cb1ee6503 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -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(); +/// All currently implemented targets. +const List _kDefaultTargets = [ + 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 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 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 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 runCommand() { - try { - final List> results = buildSystem.describe(targetName, environment); - for (Map result in results) { - if (result['name'] == targetName) { - final List 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 runCommand() { - printStatus(environment.buildDir.path); - return null; - } -} - diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 0cbab98d6d2..f086f2850ca 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -69,7 +69,7 @@ Future runInContext( Artifacts: () => CachedArtifacts(), AssetBundleFactory: () => AssetBundleFactory.defaultInstance, BotDetector: () => const BotDetector(), - BuildSystem: () => BuildSystem(), + BuildSystem: () => const BuildSystem(), Cache: () => Cache(), ChromeLauncher: () => const ChromeLauncher(), CocoaPods: () => CocoaPods(), diff --git a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart index 95ae9996dd1..ca49ef0af4f 100644 --- a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart @@ -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,515 +19,217 @@ void main() { Cache.disableLocking(); }); - group(Target, () { - Testbed testbed; - MockPlatform mockPlatform; - Environment environment; - Target fooTarget; - Target barTarget; - Target fizzTarget; - BuildSystem buildSystem; - int fooInvocations; - int barInvocations; + const BuildSystem buildSystem = BuildSystem(); + Testbed testbed; + MockPlatform mockPlatform; + Environment environment; + Target fooTarget; + Target barTarget; + Target fizzTarget; + Target sharedTarget; + int fooInvocations; + int barInvocations; + int shared; - setUp(() { - fooInvocations = 0; - barInvocations = 0; - mockPlatform = MockPlatform(); - // Keep file paths the same. - when(mockPlatform.isWindows).thenReturn(false); - testbed = Testbed( - setup: () { - environment = Environment( - projectDir: fs.currentDirectory, - ); - fs.file('foo.dart').createSync(recursive: true); - fs.file('pubspec.yaml').createSync(); - fooTarget = Target( - name: 'foo', - inputs: const [ - Source.pattern('{PROJECT_DIR}/foo.dart'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/out'), - ], - dependencies: [], - buildAction: (Map updates, Environment environment) { - environment - .buildDir - .childFile('out') - ..createSync(recursive: true) - ..writeAsStringSync('hey'); - fooInvocations++; - } - ); - barTarget = Target( - name: 'bar', - inputs: const [ - Source.pattern('{BUILD_DIR}/out'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/bar'), - ], - dependencies: [fooTarget], - buildAction: (Map updates, Environment environment) { - environment.buildDir - .childFile('bar') - ..createSync(recursive: true) - ..writeAsStringSync('there'); - barInvocations++; - } - ); - fizzTarget = Target( - name: 'fizz', - inputs: const [ - Source.pattern('{BUILD_DIR}/out'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/fizz'), - ], - dependencies: [fooTarget], - buildAction: (Map updates, Environment environment) { - throw Exception('something bad happens'); - } - ); - buildSystem = BuildSystem({ - fooTarget.name: fooTarget, - barTarget.name: barTarget, - fizzTarget.name: fizzTarget, - }); - }, - overrides: { - Platform: () => mockPlatform, - } - ); - }); + setUp(() { + fooInvocations = 0; + barInvocations = 0; + shared = 0; + mockPlatform = MockPlatform(); + // Keep file paths the same. + when(mockPlatform.isWindows).thenReturn(false); - test('can describe build rules', () => testbed.run(() { - expect(buildSystem.describe('foo', environment), [ - { - 'name': 'foo', - 'dependencies': [], - 'inputs': ['/foo.dart'], - 'outputs': [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())); - })); - - test('Throws exception if asked to build with missing inputs', () => testbed.run(() async { - // Delete required input file. - fs.file('foo.dart').deleteSync(); - final BuildResult buildResult = await buildSystem.build('foo', environment, const BuildSystemConfig()); - - expect(buildResult.hasException, true); - expect(buildResult.exceptions.values.single.exception, isInstanceOf()); - })); - - test('Throws exception if it does not produce a specified output', () => testbed.run(() async { - final Target badTarget = Target - (buildAction: (Map inputs, Environment environment) {}, - inputs: const [ - Source.pattern('{PROJECT_DIR}/foo.dart'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/out') - ], - name: 'bad' - ); - buildSystem = BuildSystem({ - badTarget.name: badTarget, - }); - final BuildResult result = await buildSystem.build('bad', environment, const BuildSystemConfig()); - - expect(result.hasException, true); - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); - - test('Saves a stamp file with inputs and outputs', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - final File stampFile = fs.file(fs.path.join(environment.buildDir.path, 'foo.stamp')); - expect(stampFile.existsSync(), true); - - final Map stampContents = json.decode(stampFile.readAsStringSync()); - expect(stampContents['inputs'], ['/foo.dart']); - })); - - test('Does not re-invoke build if stamp is valid', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - expect(fooInvocations, 1); - })); - - test('Re-invoke build if input is modified', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - fs.file('foo.dart').writeAsStringSync('new contents'); - - await buildSystem.build('foo', environment, const BuildSystemConfig()); - expect(fooInvocations, 2); - })); - - test('does not re-invoke build if input timestamp changes', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - fs.file('foo.dart').writeAsStringSync(''); - - await buildSystem.build('foo', environment, const BuildSystemConfig()); - expect(fooInvocations, 1); - })); - - test('does not re-invoke build if output timestamp changes', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - environment.buildDir.childFile('out').writeAsStringSync('hey'); - - await buildSystem.build('foo', environment, const BuildSystemConfig()); - expect(fooInvocations, 1); - })); - - - test('Re-invoke build if output is modified', () => testbed.run(() async { - await buildSystem.build('foo', environment, const BuildSystemConfig()); - - environment.buildDir.childFile('out').writeAsStringSync('Something different'); - - await buildSystem.build('foo', environment, const BuildSystemConfig()); - expect(fooInvocations, 2); - })); - - test('Runs dependencies of targets', () => testbed.run(() async { - await buildSystem.build('bar', environment, const BuildSystemConfig()); - - expect(fs.file(fs.path.join(environment.buildDir.path, 'bar')).existsSync(), true); - expect(fooInvocations, 1); - expect(barInvocations, 1); - })); - - test('handles a throwing build action', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('fizz', environment, const BuildSystemConfig()); - - expect(result.hasException, true); - })); - - test('Can describe itself with JSON output', () => testbed.run(() { - environment.buildDir.createSync(recursive: true); - expect(fooTarget.toJson(environment), { - 'inputs': [ - '/foo.dart' - ], - 'outputs': [ - fs.path.join(environment.buildDir.path, 'out'), - ], - 'dependencies': [], - 'name': 'foo', - 'stamp': fs.path.join(environment.buildDir.path, 'foo.stamp'), - }); - })); - - test('Compute update recognizes added files', () => testbed.run(() async { - fs.directory('build').createSync(); - final FileHashStore fileCache = FileHashStore(environment); - fileCache.initialize(); - final List inputs = fooTarget.resolveInputs(environment); - final Map changes = await fooTarget.computeChanges(inputs, environment, fileCache); - fileCache.persist(); - - expect(changes, { - '/foo.dart': ChangeType.Added - }); - - await buildSystem.build('foo', environment, const BuildSystemConfig()); - final Map secondChanges = await fooTarget.computeChanges(inputs, environment, fileCache); - - expect(secondChanges, {}); - })); - }); - - group('FileCache', () { - Testbed testbed; - Environment environment; - - setUp(() { - testbed = Testbed(setup: () { - fs.directory('build').createSync(); + /// Create various testing targets. + fooTarget = TestTarget((List inputFiles, Environment environment) async { + environment + .buildDir + .childFile('out') + ..createSync(recursive: true) + ..writeAsStringSync('hey'); + fooInvocations++; + }) + ..name = 'foo' + ..inputs = const [ + Source.pattern('{PROJECT_DIR}/foo.dart'), + ] + ..outputs = const [ + Source.pattern('{BUILD_DIR}/out'), + ] + ..dependencies = []; + barTarget = TestTarget((List inputFiles, Environment environment) async { + environment.buildDir + .childFile('bar') + ..createSync(recursive: true) + ..writeAsStringSync('there'); + barInvocations++; + }) + ..name = 'bar' + ..inputs = const [ + Source.pattern('{BUILD_DIR}/out'), + ] + ..outputs = const [ + Source.pattern('{BUILD_DIR}/bar'), + ] + ..dependencies = []; + fizzTarget = TestTarget((List inputFiles, Environment environment) async { + throw Exception('something bad happens'); + }) + ..name = 'fizz' + ..inputs = const [ + Source.pattern('{BUILD_DIR}/out'), + ] + ..outputs = const [ + Source.pattern('{BUILD_DIR}/fizz'), + ] + ..dependencies = [fooTarget]; + sharedTarget = TestTarget((List inputFiles, Environment environment) async { + shared += 1; + }) + ..name = 'shared' + ..inputs = const [ + Source.pattern('{PROJECT_DIR}/foo.dart'), + ]; + testbed = Testbed( + setup: () { 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 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]); - fileCache.persist(); - final String currentHash = fileCache.currentHashes[file.resolveSymbolicLinksSync()]; - final List 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()); - })); + fs.file('foo.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''); + fs.file('pubspec.yaml').createSync(); + }, + overrides: { + Platform: () => mockPlatform, + } + ); }); - group('Target', () { - Testbed testbed; - MockPlatform mockPlatform; - Environment environment; - Target sharedTarget; - BuildSystem buildSystem; - int shared; + 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(fooTarget, environment); - 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.pattern('{PROJECT_DIR}/foo.dart'), - ], - outputs: const [], - dependencies: [], - buildAction: (Map updates, Environment environment) { - shared += 1; - } - ); - final Target fooTarget = Target( - name: 'foo', - inputs: const [ - Source.pattern('{PROJECT_DIR}/foo.dart'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/out'), - ], - dependencies: [sharedTarget], - buildAction: (Map updates, Environment environment) { - environment - .buildDir - .childFile('out') - ..createSync(recursive: true) - ..writeAsStringSync('hey'); - } - ); - final Target barTarget = Target( - name: 'bar', - inputs: const [ - Source.pattern('{BUILD_DIR}/out'), - ], - outputs: const [ - Source.pattern('{BUILD_DIR}/bar'), - ], - dependencies: [fooTarget, sharedTarget], - buildAction: (Map updates, Environment environment) { - environment - .buildDir - .childFile('bar') - ..createSync(recursive: true) - ..writeAsStringSync('there'); - } - ); - buildSystem = BuildSystem({ - fooTarget.name: fooTarget, - barTarget.name: barTarget, - sharedTarget.name: sharedTarget, - }); - }, - overrides: { - Platform: () => mockPlatform, - } - ); + expect(buildResult.hasException, true); + expect(buildResult.exceptions.values.single.exception, isInstanceOf()); + })); + + test('Throws exception if it does not produce a specified output', () => testbed.run(() async { + final Target badTarget = TestTarget((List inputFiles, Environment environment) async {}) + ..inputs = const [ + Source.pattern('{PROJECT_DIR}/foo.dart'), + ] + ..outputs = const [ + Source.pattern('{BUILD_DIR}/out') + ]; + final BuildResult result = await buildSystem.build(badTarget, environment); + + expect(result.hasException, true); + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + test('Saves a stamp file with inputs and outputs', () => testbed.run(() async { + await buildSystem.build(fooTarget, environment); + + final File stampFile = fs.file(fs.path.join(environment.buildDir.path, 'foo.stamp')); + expect(stampFile.existsSync(), true); + + final Map stampContents = json.decode(stampFile.readAsStringSync()); + expect(stampContents['inputs'], ['/foo.dart']); + })); + + test('Does not re-invoke build if stamp is valid', () => testbed.run(() async { + 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(fooTarget, environment); + + fs.file('foo.dart').writeAsStringSync('new contents'); + + await buildSystem.build(fooTarget, environment); + expect(fooInvocations, 2); + })); + + test('does not re-invoke build if input timestamp changes', () => testbed.run(() async { + await buildSystem.build(fooTarget, environment); + + fs.file('foo.dart').writeAsStringSync(''); + + await buildSystem.build(fooTarget, environment); + expect(fooInvocations, 1); + })); + + test('does not re-invoke build if output timestamp changes', () => testbed.run(() async { + await buildSystem.build(fooTarget, environment); + + environment.buildDir.childFile('out').writeAsStringSync('hey'); + + await buildSystem.build(fooTarget, environment); + expect(fooInvocations, 1); + })); + + + test('Re-invoke build if output is modified', () => testbed.run(() async { + await buildSystem.build(fooTarget, environment); + + environment.buildDir.childFile('out').writeAsStringSync('Something different'); + + await buildSystem.build(fooTarget, environment); + expect(fooInvocations, 2); + })); + + test('Runs dependencies of targets', () => testbed.run(() async { + 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(fizzTarget, environment); + + expect(result.hasException, true); + })); + + test('Can describe itself with JSON output', () => testbed.run(() { + environment.buildDir.createSync(recursive: true); + expect(fooTarget.toJson(environment), { + 'inputs': [ + '/foo.dart' + ], + 'outputs': [ + fs.path.join(environment.buildDir.path, 'out'), + ], + 'dependencies': [], + 'name': 'foo', + 'stamp': fs.path.join(environment.buildDir.path, 'foo.stamp'), }); - - 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) => []).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())); - })); - - - test('can\'t substitute foo', () => testbed.run(() { - const Source invalidBase = Source.pattern('foo'); - - expect(() => invalidBase.accept(visitor), throwsA(isInstanceOf())); - })); - }); - - + })); test('Can find dependency cycles', () { - final Target barTarget = Target( - name: 'bar', - inputs: [], - outputs: [], - buildAction: null, - dependencies: nonconst([]) - ); - final Target fooTarget = Target( - name: 'foo', - inputs: [], - outputs: [], - buildAction: null, - dependencies: nonconst([]) - ); + 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())); }); } @@ -541,14 +239,23 @@ class MockPlatform extends Mock implements Platform {} // Work-around for silly lint check. T nonconst(T input) => input; -class TestBehavior extends SourceBehavior { - @override - List inputs(Environment environment) { - return null; - } +class TestTarget extends Target { + TestTarget([this._build]); + + final Future Function(List inputFiles, Environment environment) _build; @override - List outputs(Environment environment) { - return null; - } + Future build(List inputFiles, Environment environment) => _build(inputFiles, environment); + + @override + List dependencies = []; + + @override + List inputs = []; + + @override + String name = 'test'; + + @override + List outputs = []; } diff --git a/packages/flutter_tools/test/general.shard/build_system/exceptions_test.dart b/packages/flutter_tools/test/general.shard/build_system/exceptions_test.dart index 18de1fdeec1..2ee30119fcd 100644 --- a/packages/flutter_tools/test/general.shard/build_system/exceptions_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/exceptions_test.dart @@ -12,19 +12,9 @@ void main() { test('Exceptions', () { final MissingInputException missingInputException = MissingInputException( [fs.file('foo'), fs.file('bar')], 'example'); - final CycleException cycleException = CycleException(const { - Target( - name: 'foo', - buildAction: null, - inputs: [], - outputs: [], - ), - Target( - name: 'bar', - buildAction: null, - inputs: [], - outputs: [], - ) + final CycleException cycleException = CycleException({ + 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 Function(List inputFiles, Environment environment) _build; + + @override + Future build(List inputFiles, Environment environment) => _build(inputFiles, environment); + + @override + List dependencies = []; + + @override + List inputs = []; + + @override + String name = 'test'; + + @override + List outputs = []; +} diff --git a/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart b/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart new file mode 100644 index 00000000000..361934b6209 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart @@ -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 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]); + fileCache.persist(); + final String currentHash = fileCache.currentHashes[file.resolveSymbolicLinksSync()]; + final List 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()); + })); +} diff --git a/packages/flutter_tools/test/general.shard/build_system/source_test.dart b/packages/flutter_tools/test/general.shard/build_system/source_test.dart new file mode 100644 index 00000000000..d49b2e31c28 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/source_test.dart @@ -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) => []).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())); + })); + + + test('can\'t substitute foo', () => testbed.run(() { + const Source invalidBase = Source.pattern('foo'); + + expect(() => invalidBase.accept(visitor), throwsA(isInstanceOf())); + })); +} + +class TestBehavior extends SourceBehavior { + @override + List inputs(Environment environment) { + return null; + } + + @override + List outputs(Environment environment) { + return null; + } +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart index a5b20345ae5..2541115f945 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart @@ -10,61 +10,59 @@ import '../../../src/common.dart'; import '../../../src/testbed.dart'; void main() { - group('copy_assets', () { - Testbed testbed; - BuildSystem buildSystem; - Environment environment; + const BuildSystem buildSystem = BuildSystem(); + Environment environment; + Testbed testbed; - setUp(() { - testbed = Testbed(setup: () { - environment = Environment( - projectDir: fs.currentDirectory, - ); - buildSystem = BuildSystem({ - copyAssets.name: copyAssets, - }); - fs.file(fs.path.join('assets', 'foo', 'bar.png')) - ..createSync(recursive: true); - fs.file('.packages') - ..createSync(); - fs.file('pubspec.yaml') - ..createSync() - ..writeAsStringSync(''' -name: example - -flutter: - assets: - - assets/foo/bar.png -'''); - }); - }); - - test('Copies files to correct asset directory', () => testbed.run(() async { - await buildSystem.build('copy_assets', environment, const BuildSystemConfig()); - - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'AssetManifest.json')).existsSync(), true); - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'FontManifest.json')).existsSync(), true); - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'LICENSE')).existsSync(), true); - // See https://github.com/flutter/flutter/issues/35293 - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true); - })); - - test('Does not leave stale files in build directory', () => testbed.run(() async { - await buildSystem.build('copy_assets', environment, const BuildSystemConfig()); - - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true); - // Modify manifest to remove asset. + setUp(() { + testbed = Testbed(setup: () { + environment = Environment( + projectDir: fs.currentDirectory, + ); + 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') + ..createSync(); fs.file('pubspec.yaml') ..createSync() ..writeAsStringSync(''' name: example flutter: + assets: + - assets/foo/bar.png '''); - await buildSystem.build('copy_assets', environment, const BuildSystemConfig()); - - // See https://github.com/flutter/flutter/issues/35293 - expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), false); - })); + }); }); + + test('Copies files to correct asset directory', () => testbed.run(() async { + 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); + expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'LICENSE')).existsSync(), true); + // See https://github.com/flutter/flutter/issues/35293 + expect(fs.file(fs.path.join(environment.buildDir.path, 'flutter_assets', 'assets/foo/bar.png')).existsSync(), true); + })); + + test('Does not leave stale files in build directory', () => testbed.run(() async { + await buildSystem.build(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. + fs.file('pubspec.yaml') + ..createSync() + ..writeAsStringSync(''' +name: example + +flutter: +'''); + 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); + })); } diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart index 74064eec3c9..4d73747c921 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart @@ -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,221 +23,234 @@ import '../../../src/mocks.dart'; import '../../../src/testbed.dart'; void main() { - group('dart rules', () { - Testbed testbed; - BuildSystem buildSystem; - Environment androidEnvironment; - Environment iosEnvironment; - MockProcessManager mockProcessManager; - MockXcode mockXcode; + const BuildSystem buildSystem = BuildSystem(); + Testbed testbed; + Environment androidEnvironment; + Environment iosEnvironment; + MockProcessManager mockProcessManager; + MockXcode mockXcode; - setUpAll(() { - Cache.disableLocking(); - }); + setUpAll(() { + Cache.disableLocking(); + }); - setUp(() { - mockProcessManager = MockProcessManager(); - mockXcode = MockXcode(); - testbed = Testbed(setup: () { - androidEnvironment = Environment( - projectDir: fs.currentDirectory, - defines: { - kBuildMode: getNameForBuildMode(BuildMode.profile), - kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), - } - ); - iosEnvironment = Environment( - projectDir: fs.currentDirectory, - defines: { - kBuildMode: getNameForBuildMode(BuildMode.profile), - kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios), - } - ); - buildSystem = BuildSystem(); - HostPlatform hostPlatform; - if (platform.isWindows) { - hostPlatform = HostPlatform.windows_x64; - } else if (platform.isLinux) { - hostPlatform = HostPlatform.linux_x64; - } else if (platform.isMacOS) { - hostPlatform = HostPlatform.darwin_x64; - } else { - assert(false); + setUp(() { + mockXcode = MockXcode(); + mockProcessManager = MockProcessManager(); + testbed = Testbed(setup: () { + androidEnvironment = Environment( + projectDir: fs.currentDirectory, + defines: { + kBuildMode: getNameForBuildMode(BuildMode.profile), + kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm), } - final String skyEngineLine = platform.isWindows - ? r'sky_engine:file:///C:/bin/cache/pkg/sky_engine/lib/' - : 'sky_engine:file:///bin/cache/pkg/sky_engine/lib/'; - fs.file('.packages') - ..createSync() - ..writeAsStringSync(''' + ); + iosEnvironment = Environment( + projectDir: fs.currentDirectory, + defines: { + kBuildMode: getNameForBuildMode(BuildMode.profile), + kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios), + } + ); + HostPlatform hostPlatform; + if (platform.isWindows) { + hostPlatform = HostPlatform.windows_x64; + } else if (platform.isLinux) { + hostPlatform = HostPlatform.linux_x64; + } else if (platform.isMacOS) { + hostPlatform = HostPlatform.darwin_x64; + } else { + assert(false); + } + final String skyEngineLine = platform.isWindows + ? r'sky_engine:file:///C:/bin/cache/pkg/sky_engine/lib/' + : 'sky_engine:file:///bin/cache/pkg/sky_engine/lib/'; + fs.file('.packages') + ..createSync() + ..writeAsStringSync(''' # Generated $skyEngineLine flutter_tools:lib/'''); - final String engineArtifacts = fs.path.join('bin', 'cache', - 'artifacts', 'engine'); - final List paths = [ - fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui', - 'ui.dart'), - fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext', - 'vmservice_io.dart'), - fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - fs.path.join(engineArtifacts, getNameForHostPlatform(hostPlatform), - 'frontend_server.dart.snapshot'), - fs.path.join(engineArtifacts, 'android-arm-profile', - getNameForHostPlatform(hostPlatform), 'gen_snapshot'), - fs.path.join(engineArtifacts, 'ios-profile', 'gen_snapshot'), - fs.path.join(engineArtifacts, 'common', 'flutter_patched_sdk', - 'platform_strong.dill'), - fs.path.join('lib', 'foo.dart'), - fs.path.join('lib', 'bar.dart'), - fs.path.join('lib', 'fizz'), - ]; - for (String path in paths) { - fs.file(path).createSync(recursive: true); - } - }, overrides: { - KernelCompilerFactory: () => FakeKernelCompilerFactory(), - GenSnapshot: () => FakeGenSnapshot(), - }); + final String engineArtifacts = fs.path.join('bin', 'cache', + 'artifacts', 'engine'); + final List paths = [ + fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui', + 'ui.dart'), + fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext', + 'vmservice_io.dart'), + fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), + fs.path.join(engineArtifacts, getNameForHostPlatform(hostPlatform), + 'frontend_server.dart.snapshot'), + fs.path.join(engineArtifacts, 'android-arm-profile', + getNameForHostPlatform(hostPlatform), 'gen_snapshot'), + fs.path.join(engineArtifacts, 'ios-profile', 'gen_snapshot'), + fs.path.join(engineArtifacts, 'common', 'flutter_patched_sdk', + 'platform_strong.dill'), + fs.path.join('lib', 'foo.dart'), + fs.path.join('lib', 'bar.dart'), + fs.path.join('lib', 'fizz'), + 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); + } + }, overrides: { + KernelCompilerFactory: () => FakeKernelCompilerFactory(), + GenSnapshot: () => FakeGenSnapshot(), + }); + }); + + test('kernel_snapshot Produces correct output directory', () => testbed.run(() async { + await buildSystem.build(const KernelSnapshot(), androidEnvironment); + + 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(const KernelSnapshot(), + androidEnvironment..defines.remove(kBuildMode)); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + test('aot_elf_profile Produces correct output directory', () => testbed.run(() async { + await buildSystem.build(const AotElfProfile(), androidEnvironment); + + 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(const AotElfProfile(), + androidEnvironment..defines.remove(kBuildMode)); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + + test('aot_elf_profile throws error if missing target platform', () => testbed.run(() async { + final BuildResult result = await buildSystem.build(const AotElfProfile(), + androidEnvironment..defines.remove(kTargetPlatform)); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + + test('aot_assembly_profile throws error if missing build mode', () => testbed.run(() async { + final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), + iosEnvironment..defines.remove(kBuildMode)); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + test('aot_assembly_profile throws error if missing target platform', () => testbed.run(() async { + final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), + iosEnvironment..defines.remove(kTargetPlatform)); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + test('aot_assembly_profile throws error if built for non-iOS platform', () => testbed.run(() async { + final BuildResult result = await buildSystem + .build(const AotAssemblyProfile(), androidEnvironment); + + expect(result.exceptions.values.single.exception, isInstanceOf()); + })); + + 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: { + 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'; + + final FakeProcessResult fakeProcessResult = FakeProcessResult( + stdout: '', + stderr: '', + ); + final RunResult fakeRunResult = RunResult(fakeProcessResult, const ['foo']); + when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { + fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) + .createSync(recursive: true); + return fakeProcessResult; }); - test('kernel_snapshot Produces correct output directory', () => testbed.run(() async { - await buildSystem.build('kernel_snapshot', androidEnvironment, const BuildSystemConfig()); + when(mockXcode.cc(any)).thenAnswer((_) => Future.value(fakeRunResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future.value(fakeRunResult)); + when(mockXcode.dsymutil(any)).thenAnswer((_) => Future.value(fakeRunResult)); - expect(fs.file(fs.path.join(androidEnvironment.buildDir.path,'main.app.dill')).existsSync(), true); - })); + final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment); - test('kernel_snapshot throws error if missing build mode', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('kernel_snapshot', - androidEnvironment..defines.remove(kBuildMode), const BuildSystemConfig()); + expect(result.success, true); + verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1); + verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(1); + verify(mockXcode.dsymutil(any)).called(1); + }, overrides: { + ProcessManager: () => mockProcessManager, + Xcode: () => mockXcode, + })); - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); + test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async { + iosEnvironment.defines[kIosArchs] = 'armv7,arm64'; + iosEnvironment.defines[kBitcodeFlag] = 'true'; - test('aot_elf_profile Produces correct output directory', () => testbed.run(() async { - await buildSystem.build('aot_elf_profile', androidEnvironment, const BuildSystemConfig()); + final FakeProcessResult fakeProcessResult = FakeProcessResult( + stdout: '', + stderr: '', + ); + final RunResult fakeRunResult = RunResult(fakeProcessResult, const ['foo']); + when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { + fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) + .createSync(recursive: true); + return fakeProcessResult; + }); - expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'main.app.dill')).existsSync(), true); - expect(fs.file(fs.path.join(androidEnvironment.buildDir.path, 'app.so')).existsSync(), true); - })); + when(mockXcode.cc(any)).thenAnswer((_) => Future.value(fakeRunResult)); + when(mockXcode.clang(any)).thenAnswer((_) => Future.value(fakeRunResult)); + when(mockXcode.dsymutil(any)).thenAnswer((_) => Future.value(fakeRunResult)); - 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 AotAssemblyProfile(), iosEnvironment); - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); + expect(result.success, true); + verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2); + verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2); + verify(mockXcode.dsymutil(any)).called(2); + }, overrides: { + ProcessManager: () => mockProcessManager, + Xcode: () => mockXcode, + })); - - test('aot_elf_profile throws error if missing target platform', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('aot_elf_profile', - androidEnvironment..defines.remove(kTargetPlatform), const BuildSystemConfig()); - - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); - - - test('aot_assembly_profile throws error if missing build mode', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('aot_assembly_profile', - iosEnvironment..defines.remove(kBuildMode), const BuildSystemConfig()); - - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); - - test('aot_assembly_profile throws error if missing target platform', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('aot_assembly_profile', - iosEnvironment..defines.remove(kTargetPlatform), const BuildSystemConfig()); - - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); - - test('aot_assembly_profile throws error if built for non-iOS platform', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('aot_assembly_profile', - androidEnvironment, const BuildSystemConfig()); - - expect(result.exceptions.values.single.exception, isInstanceOf()); - })); - - test('aot_assembly_profile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] = 'arm64'; - iosEnvironment.defines[kBitcodeFlag] = 'true'; - - final FakeProcessResult fakeProcessResult = FakeProcessResult( + 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 RunResult fakeRunResult = RunResult(fakeProcessResult, const ['foo']); - when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { - fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) - .createSync(recursive: true); - return fakeProcessResult; - }); + }); + final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment); - when(mockXcode.cc(any)).thenAnswer((_) => Future.value(fakeRunResult)); - when(mockXcode.clang(any)).thenAnswer((_) => Future.value(fakeRunResult)); - when(mockXcode.dsymutil(any)).thenAnswer((_) => Future.value(fakeRunResult)); - - final BuildResult result = await buildSystem.build('aot_assembly_profile', - iosEnvironment, const BuildSystemConfig()); - - expect(result.success, true); - verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1); - verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(1); - verify(mockXcode.dsymutil(any)).called(1); - }, overrides: { - ProcessManager: () => mockProcessManager, - Xcode: () => mockXcode, - })); - - test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] = 'armv7,arm64'; - iosEnvironment.defines[kBitcodeFlag] = 'true'; - - final FakeProcessResult fakeProcessResult = FakeProcessResult( - stdout: '', - stderr: '', - ); - final RunResult fakeRunResult = RunResult(fakeProcessResult, const ['foo']); - when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { - fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) - .createSync(recursive: true); - return fakeProcessResult; - }); - - when(mockXcode.cc(any)).thenAnswer((_) => Future.value(fakeRunResult)); - when(mockXcode.clang(any)).thenAnswer((_) => Future.value(fakeRunResult)); - when(mockXcode.dsymutil(any)).thenAnswer((_) => Future.value(fakeRunResult)); - - final BuildResult result = await buildSystem.build('aot_assembly_profile', - iosEnvironment, const BuildSystemConfig()); - - expect(result.success, true); - verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2); - verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2); - verify(mockXcode.dsymutil(any)).called(2); - }, overrides: { - ProcessManager: () => mockProcessManager, - Xcode: () => mockXcode, - })); - - test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async { - iosEnvironment.defines[kIosArchs] = 'armv7,arm64'; - when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { - fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App')) - .createSync(recursive: true); - return FakeProcessResult( - stdout: '', - stderr: '', - ); - }); - final BuildResult result = await buildSystem.build('aot_assembly_profile', - iosEnvironment, const BuildSystemConfig()); - - expect(result.success, true); - }, overrides: { - ProcessManager: () => mockProcessManager, - })); - }); + expect(result.success, true); + }, overrides: { + ProcessManager: () => mockProcessManager, + })); } class MockProcessManager extends Mock implements ProcessManager {} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart index 801f12ac966..b17ed5b8285 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart @@ -13,72 +13,72 @@ import '../../../src/common.dart'; import '../../../src/testbed.dart'; void main() { - group('unpack_linux', () { - Testbed testbed; - BuildSystem buildSystem; - Environment environment; - MockPlatform mockPlatform; + Testbed testbed; + const BuildSystem buildSystem = BuildSystem(); + Environment environment; + MockPlatform mockPlatform; - setUpAll(() { - Cache.disableLocking(); - }); - - setUp(() { - mockPlatform = MockPlatform(); - when(mockPlatform.isWindows).thenReturn(false); - when(mockPlatform.isMacOS).thenReturn(false); - when(mockPlatform.isLinux).thenReturn(true); - testbed = Testbed(setup: () { - Cache.flutterRoot = ''; - environment = Environment( - projectDir: fs.currentDirectory, - ); - buildSystem = BuildSystem({ - unpackLinux.name: unpackLinux, - }); - fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').createSync(recursive: true); - fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync(); - fs.file('bin/cache/artifacts/engine/linux-x64/flutter_messenger.h').createSync(); - fs.file('bin/cache/artifacts/engine/linux-x64/flutter_plugin_registrar.h').createSync(); - fs.file('bin/cache/artifacts/engine/linux-x64/flutter_glfw.h').createSync(); - fs.file('bin/cache/artifacts/engine/linux-x64/icudtl.dat').createSync(); - fs.file('bin/cache/artifacts/engine/linux-x64/cpp_client_wrapper/foo').createSync(recursive: true); - fs.directory('linux').createSync(); - }, overrides: { - Platform: () => mockPlatform, - }); - }); - - test('Copies files to correct cache directory', () => testbed.run(() async { - final BuildResult result = await buildSystem.build('unpack_linux', environment, const BuildSystemConfig()); - - expect(result.hasException, false); - expect(fs.file('linux/flutter/libflutter_linux.so').existsSync(), true); - expect(fs.file('linux/flutter/flutter_export.h').existsSync(), true); - expect(fs.file('linux/flutter/flutter_messenger.h').existsSync(), true); - expect(fs.file('linux/flutter/flutter_plugin_registrar.h').existsSync(), true); - expect(fs.file('linux/flutter/flutter_glfw.h').existsSync(), true); - expect(fs.file('linux/flutter/icudtl.dat').existsSync(), true); - expect(fs.file('linux/flutter/cpp_client_wrapper/foo').existsSync(), true); - })); - - test('Does not re-copy files unecessarily', () => testbed.run(() async { - await buildSystem.build('unpack_linux', environment, const BuildSystemConfig()); - final DateTime modified = fs.file('linux/flutter/libflutter_linux.so').statSync().modified; - await buildSystem.build('unpack_linux', environment, const BuildSystemConfig()); - - expect(fs.file('linux/flutter/libflutter_linux.so').statSync().modified, equals(modified)); - })); - - test('Detects changes in input cache files', () => testbed.run(() async { - await buildSystem.build('unpack_linux', environment, const BuildSystemConfig()); - fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').writeAsStringSync('asd'); // modify cache. - - await buildSystem.build('unpack_linux', environment, const BuildSystemConfig()); - - expect(fs.file('linux/flutter/libflutter_linux.so').readAsStringSync(), 'asd'); - })); + setUpAll(() { + Cache.disableLocking(); + Cache.flutterRoot = ''; }); + + setUp(() { + mockPlatform = MockPlatform(); + when(mockPlatform.isWindows).thenReturn(false); + when(mockPlatform.isMacOS).thenReturn(false); + when(mockPlatform.isLinux).thenReturn(true); + testbed = Testbed(setup: () { + Cache.flutterRoot = ''; + environment = Environment( + projectDir: fs.currentDirectory, + ); + fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').createSync(recursive: true); + fs.file('bin/cache/artifacts/engine/linux-x64/flutter_export.h').createSync(); + fs.file('bin/cache/artifacts/engine/linux-x64/flutter_messenger.h').createSync(); + fs.file('bin/cache/artifacts/engine/linux-x64/flutter_plugin_registrar.h').createSync(); + fs.file('bin/cache/artifacts/engine/linux-x64/flutter_glfw.h').createSync(); + fs.file('bin/cache/artifacts/engine/linux-x64/icudtl.dat').createSync(); + fs.file('bin/cache/artifacts/engine/linux-x64/cpp_client_wrapper/foo').createSync(recursive: true); + fs.file('packages/flutter_tools/lib/src/build_system/targets/linux.dart').createSync(recursive: true); + fs.directory('linux').createSync(); + }, overrides: { + Platform: () => mockPlatform, + }); + }); + + test('Copies files to correct cache directory', () => testbed.run(() async { + final BuildResult result = await buildSystem.build(const UnpackLinux(), environment); + + expect(result.hasException, false); + expect(fs.file('linux/flutter/libflutter_linux.so').existsSync(), true); + expect(fs.file('linux/flutter/flutter_export.h').existsSync(), true); + expect(fs.file('linux/flutter/flutter_messenger.h').existsSync(), true); + expect(fs.file('linux/flutter/flutter_plugin_registrar.h').existsSync(), true); + expect(fs.file('linux/flutter/flutter_glfw.h').existsSync(), true); + expect(fs.file('linux/flutter/icudtl.dat').existsSync(), true); + expect(fs.file('linux/flutter/cpp_client_wrapper/foo').existsSync(), true); + })); + + test('Does not re-copy files unecessarily', () => testbed.run(() async { + await buildSystem.build(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(theDistantPast)); + })); + + test('Detects changes in input cache files', () => testbed.run(() async { + await buildSystem.build(const UnpackLinux(), environment); + fs.file('bin/cache/artifacts/engine/linux-x64/libflutter_linux.so').writeAsStringSync('asd'); // modify cache. + + await buildSystem.build(const UnpackLinux(), environment); + + expect(fs.file('linux/flutter/libflutter_linux.so').readAsStringSync(), 'asd'); + })); } class MockPlatform extends Mock implements Platform {} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart index 6d656b3eda0..554f1477ba5 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart @@ -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,86 +16,87 @@ import '../../../src/common.dart'; import '../../../src/testbed.dart'; void main() { - group('unpack_macos', () { - Testbed testbed; - BuildSystem buildSystem; - Environment environment; - MockPlatform mockPlatform; + Testbed testbed; + const BuildSystem buildSystem = BuildSystem(); + Environment environment; + MockPlatform mockPlatform; - setUp(() { - mockPlatform = MockPlatform(); - when(mockPlatform.isWindows).thenReturn(false); - when(mockPlatform.isMacOS).thenReturn(true); - when(mockPlatform.isLinux).thenReturn(false); - testbed = Testbed(setup: () { - environment = Environment( - projectDir: fs.currentDirectory, - ); - buildSystem = BuildSystem({ - unpackMacos.name: unpackMacos, - }); - final List inputs = [ - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/FlutterMacOS'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEReshapeListener.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEView.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEViewController.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterChannels.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterCodecs.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterMacOS.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Modules/module.modulemap'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/icudtl.dat'), - fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/info.plist'), - ]; - for (File input in inputs) { - input.createSync(recursive: true); - } - when(processManager.runSync(any)).thenAnswer((Invocation invocation) { - final List arguments = invocation.positionalArguments.first; - final Directory source = fs.directory(arguments[arguments.length - 2]); - final Directory target = fs.directory(arguments.last) - ..createSync(recursive: true); - for (FileSystemEntity entity in source.listSync(recursive: true)) { - if (entity is File) { - final String relative = fs.path.relative(entity.path, from: source.path); - final String destination = fs.path.join(target.path, relative); - if (!fs.file(destination).parent.existsSync()) { - fs.file(destination).parent.createSync(); - } - entity.copySync(destination); - } - } - return FakeProcessResult()..exitCode = 0; - }); - }, overrides: { - ProcessManager: () => MockProcessManager(), - Platform: () => mockPlatform, - }); - }); - - test('Copies files to correct cache directory', () => testbed.run(() async { - await buildSystem.build('unpack_macos', environment, const BuildSystemConfig()); - - expect(fs.directory('macos/Flutter/FlutterMacOS.framework').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/FlutterMacOS').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEReshapeListener.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEView.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEViewController.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterChannels.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterCodecs.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterMacOS.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Modules/module.modulemap').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/icudtl.dat').existsSync(), true); - expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/info.plist').existsSync(), true); - })); + setUpAll(() { + Cache.disableLocking(); + Cache.flutterRoot = ''; }); + + setUp(() { + mockPlatform = MockPlatform(); + when(mockPlatform.isWindows).thenReturn(false); + when(mockPlatform.isMacOS).thenReturn(true); + when(mockPlatform.isLinux).thenReturn(false); + testbed = Testbed(setup: () { + environment = Environment( + projectDir: fs.currentDirectory, + ); + final List inputs = [ + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/FlutterMacOS'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEReshapeListener.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEView.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FLEViewController.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterChannels.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterCodecs.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterMacOS.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Modules/module.modulemap'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/icudtl.dat'), + fs.file('bin/cache/artifacts/engine/darwin-x64/FlutterMacOS.framework/Resources/info.plist'), + fs.file('packages/flutter_tools/lib/src/build_system/targets/macos.dart'), + ]; + for (File input in inputs) { + input.createSync(recursive: true); + } + when(processManager.run(any)).thenAnswer((Invocation invocation) async { + final List arguments = invocation.positionalArguments.first; + final Directory source = fs.directory(arguments[arguments.length - 2]); + final Directory target = fs.directory(arguments.last) + ..createSync(recursive: true); + for (FileSystemEntity entity in source.listSync(recursive: true)) { + if (entity is File) { + final String relative = fs.path.relative(entity.path, from: source.path); + final String destination = fs.path.join(target.path, relative); + if (!fs.file(destination).parent.existsSync()) { + fs.file(destination).parent.createSync(); + } + entity.copySync(destination); + } + } + return FakeProcessResult()..exitCode = 0; + }); + }, overrides: { + ProcessManager: () => MockProcessManager(), + Platform: () => mockPlatform, + }); + }); + + test('Copies files to correct cache directory', () => testbed.run(() async { + 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); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEReshapeListener.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEView.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FLEViewController.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterChannels.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterCodecs.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterMacOS.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Modules/module.modulemap').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/icudtl.dat').existsSync(), true); + expect(fs.file('macos/Flutter/FlutterMacOS.framework/Resources/info.plist').existsSync(), true); + })); } class MockPlatform extends Mock implements Platform {} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart index ab89ebaf520..0f447964b43 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart @@ -14,84 +14,87 @@ import '../../../src/common.dart'; import '../../../src/testbed.dart'; void main() { - group('unpack_windows', () { - Testbed testbed; - BuildSystem buildSystem; - Environment environment; - Platform platform; + Testbed testbed; + const BuildSystem buildSystem = BuildSystem(); + Environment environment; + Platform platform; - setUpAll(() { - Cache.disableLocking(); - }); - - setUp(() { - Cache.flutterRoot = ''; - platform = MockPlatform(); - when(platform.isWindows).thenReturn(true); - when(platform.isMacOS).thenReturn(false); - when(platform.isLinux).thenReturn(false); - when(platform.pathSeparator).thenReturn(r'\'); - testbed = Testbed(setup: () { - environment = Environment( - projectDir: fs.currentDirectory, - ); - buildSystem = BuildSystem({ - unpackWindows.name: unpackWindows, - }); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').createSync(recursive: true); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_glfw.h').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat').createSync(); - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo').createSync(recursive: true); - fs.directory('windows').createSync(); - }, overrides: { - FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), - Platform: () => platform, - }); - }); - - test('Copies files to correct cache directory', () => testbed.run(() async { - await buildSystem.build('unpack_windows', environment, const BuildSystemConfig()); - - expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_windows.dll').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.exp').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.lib').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.pdb').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_plugin_registrar.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\flutter_glfw.h').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\icudtl.dat').existsSync(), true); - expect(fs.file(r'C:\windows\flutter\cpp_client_wrapper\foo').existsSync(), true); - })); - - test('Does not re-copy files unecessarily', () => testbed.run(() async { - await buildSystem.build('unpack_windows', environment, const BuildSystemConfig()); - final DateTime modified = fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified; - await buildSystem.build('unpack_windows', environment, const BuildSystemConfig()); - - expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, equals(modified)); - })); - - test('Detects changes in input cache files', () => testbed.run(() async { - await buildSystem.build('unpack_windows', environment, const BuildSystemConfig()); - final DateTime modified = fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified; - fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').writeAsStringSync('asd'); // modify cache. - - await buildSystem.build('unpack_windows', environment, const BuildSystemConfig()); - - expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, isNot(modified)); - }), skip: true); // TODO(jonahwilliams): track down flakiness. + setUpAll(() { + Cache.disableLocking(); + Cache.flutterRoot = ''; }); + + setUp(() { + platform = MockPlatform(); + when(platform.isWindows).thenReturn(true); + when(platform.isMacOS).thenReturn(false); + when(platform.isLinux).thenReturn(false); + when(platform.pathSeparator).thenReturn(r'\'); + testbed = Testbed(setup: () { + environment = Environment( + projectDir: fs.currentDirectory, + ); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_export.h').createSync(recursive: true); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.exp').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.lib').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_windows.dll.pdb').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\lutter_export.h').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_messenger.h').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_plugin_registrar.h').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\flutter_glfw.h').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\icudtl.dat').createSync(); + fs.file(r'C:\bin\cache\artifacts\engine\windows-x64\cpp_client_wrapper\foo').createSync(recursive: true); + fs.file(r'C:\packages\flutter_tools\lib\src\build_system\targets\windows.dart').createSync(recursive: true); + fs.directory('windows').createSync(); + }, overrides: { + FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), + Platform: () => platform, + }); + }); + + test('Copies files to correct cache directory', () => testbed.run(() async { + 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); + expect(fs.file(r'C:\windows\flutter\flutter_windows.dll').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.exp').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.lib').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_windows.dll.pdb').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_export.h').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_messenger.h').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_plugin_registrar.h').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\flutter_glfw.h').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\icudtl.dat').existsSync(), true); + expect(fs.file(r'C:\windows\flutter\cpp_client_wrapper\foo').existsSync(), true); + })); + + test('Does not re-copy files unecessarily', () => testbed.run(() async { + await buildSystem.build(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(theDistantPast)); + })); + + test('Detects changes in input cache files', () => testbed.run(() async { + 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(const UnpackWindows(), environment); + + expect(fs.file(r'C:\windows\flutter\flutter_export.h').statSync().modified, isNot(modified)); + })); } class MockPlatform extends Mock implements Platform {} diff --git a/packages/flutter_tools/test/general.shard/commands/assemble_test.dart b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart index d8904a425bd..61ea1a83059 100644 --- a/packages/flutter_tools/test/general.shard/commands/assemble_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart @@ -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,68 +14,31 @@ import '../../src/common.dart'; import '../../src/testbed.dart'; void main() { - group('Assemble', () { - Testbed testbed; - MockBuildSystem mockBuildSystem; + Testbed testbed; + MockBuildSystem mockBuildSystem; - setUpAll(() { - Cache.disableLocking(); - }); - - setUp(() { - mockBuildSystem = MockBuildSystem(); - testbed = Testbed(overrides: { - BuildSystem: () => mockBuildSystem, - }); - }); - - test('Can list the output directory relative to project root', () => testbed.run(() async { - final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); - await commandRunner.run(['assemble', '--flutter-root=.', 'build-dir', '-dBuildMode=debug']); - final BufferLogger bufferLogger = logger; - final Environment environment = Environment( - defines: { - '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(>[ - {'fizz': 'bar'}, - ]); - final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); - await commandRunner.run(['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(>[ - {'name': 'foobar', 'inputs': ['bar', 'baz']}, - ]); - final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); - await commandRunner.run(['assemble', '--flutter-root=.', 'inputs', 'foobar']); - final BufferLogger bufferLogger = logger; - - expect(bufferLogger.statusText.trim(), 'bar\nbaz'); - })); - - test('Can run a build', () => testbed.run(() async { - when(mockBuildSystem.build('foobar', any, any)).thenAnswer((Invocation invocation) async { - return BuildResult(true, const {}, const {}); - }); - final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); - await commandRunner.run(['assemble', 'run', 'foobar']); - final BufferLogger bufferLogger = logger; - - expect(bufferLogger.statusText.trim(), 'build succeeded'); - })); + setUpAll(() { + Cache.disableLocking(); }); + + setUp(() { + mockBuildSystem = MockBuildSystem(); + testbed = Testbed(overrides: { + BuildSystem: () => mockBuildSystem, + }); + }); + + test('Can run a build', () => testbed.run(() async { + when(mockBuildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) + .thenAnswer((Invocation invocation) async { + return BuildResult(true, const {}, const {}); + }); + final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); + await commandRunner.run(['assemble', 'unpack_macos']); + final BufferLogger bufferLogger = logger; + + expect(bufferLogger.statusText.trim(), 'build succeeded'); + })); } class MockBuildSystem extends Mock implements BuildSystem {}