Flutter assemble for macos take 2! (#36987)
diff --git a/packages/flutter_tools/bin/macos_build_flutter_assets.sh b/packages/flutter_tools/bin/macos_build_flutter_assets.sh
index d576a87..1380fbf 100755
--- a/packages/flutter_tools/bin/macos_build_flutter_assets.sh
+++ b/packages/flutter_tools/bin/macos_build_flutter_assets.sh
@@ -3,8 +3,6 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# TODO(jonahwilliams): refactor this and xcode_backend.sh into one script
-# once macOS supports the same configuration as iOS.
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
@@ -33,18 +31,6 @@
target_path="${FLUTTER_TARGET}"
fi
-# Set the track widget creation flag.
-track_widget_creation_flag=""
-if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
- track_widget_creation_flag="--track-widget-creation"
-fi
-
-# Copy the framework and handle local engine builds.
-framework_name="FlutterMacOS.framework"
-ephemeral_dir="${SOURCE_ROOT}/Flutter/ephemeral"
-framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/darwin-x64"
-flutter_framework="${framework_path}/${framework_name}"
-
if [[ -n "$FLUTTER_ENGINE" ]]; then
flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
fi
@@ -63,22 +49,40 @@
exit -1
fi
local_engine_flag="--local-engine=${LOCAL_ENGINE}"
- flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/${framework_name}"
fi
-RunCommand mkdir -p -- "$ephemeral_dir"
-RunCommand rm -rf -- "${ephemeral_dir}/${framework_name}"
-RunCommand cp -Rp -- "${flutter_framework}" "${ephemeral_dir}"
-
# Set the build mode
build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
+case "$build_mode" in
+ debug)
+ build_target="debug_macos_application"
+ ;;
+ profile)
+ build_target="profile_macos_application"
+ ;;
+ release)
+ build_target="release_macos_application"
+ ;;
+ *)
+ EchoError "Unknown build mode ${build_mode}"
+ exit -1
+ ;;
+esac
+# The path where the input/output xcfilelists are stored. These are used by xcode
+# to conditionally skip this script phase if neither have changed.
+build_inputs_path="${SOURCE_ROOT}/Flutter/ephemeral/FlutterInputs.xcfilelist"
+build_outputs_path="${SOURCE_ROOT}/Flutter/ephemeral/FlutterOutputs.xcfilelist"
+
+# TODO(jonahwilliams): support flavors https://github.com/flutter/flutter/issues/32923
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \
- build bundle \
- --target-platform=darwin-x64 \
- --target="${target_path}" \
- --${build_mode} \
- ${track_widget_creation_flag} \
${flutter_engine_flag} \
- ${local_engine_flag}
+ ${local_engine_flag} \
+ assemble \
+ -dTargetFile="${target_path}" \
+ -dTargetPlatform=darwin-x64 \
+ -dBuildMode="${build_mode}" \
+ --build-inputs="${build_inputs_path}" \
+ --build-outputs="${build_outputs_path}" \
+ "${build_target}"
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index a763fc4..711092e 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -54,6 +54,8 @@
windowsDesktopPath,
/// The root of the sky_engine package
skyEnginePath,
+ /// The location of the macOS engine podspec file.
+ flutterMacOSPodspec,
}
String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMode mode ]) {
@@ -119,6 +121,8 @@
return '';
case Artifact.skyEnginePath:
return 'sky_engine';
+ case Artifact.flutterMacOSPodspec:
+ return 'FlutterMacOS.podspec';
}
assert(false, 'Invalid artifact $artifact.');
return null;
@@ -269,6 +273,7 @@
case Artifact.flutterMacOSFramework:
case Artifact.linuxDesktopPath:
case Artifact.windowsDesktopPath:
+ case Artifact.flutterMacOSPodspec:
final String engineArtifactsPath = cache.getArtifactDirectory('engine').path;
final String platformDirName = getNameForTargetPlatform(platform);
return fs.path.join(engineArtifactsPath, platformDirName, _artifactToFileName(artifact, platform, mode));
@@ -384,6 +389,8 @@
return fs.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.skyEnginePath:
return fs.path.join(_hostEngineOutPath, 'gen', 'dart-pkg', artifactFileName);
+ case Artifact.flutterMacOSPodspec:
+ return fs.path.join(_hostEngineOutPath, _artifactToFileName(artifact));
}
assert(false, 'Invalid artifact $artifact.');
return null;
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 5d0a88b..c3cf8cd 100644
--- a/packages/flutter_tools/lib/src/build_system/build_system.dart
+++ b/packages/flutter_tools/lib/src/build_system/build_system.dart
@@ -171,7 +171,7 @@
}
}
- // For each outut, first determine if we've already computed the hash
+ // For each output, 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);
@@ -436,11 +436,19 @@
/// The result information from the build system.
class BuildResult {
- BuildResult(this.success, this.exceptions, this.performance);
+ BuildResult({
+ @required this.success,
+ this.exceptions = const <String, ExceptionMeasurement>{},
+ this.performance = const <String, PerformanceMeasurement>{},
+ this.inputFiles = const <File>[],
+ this.outputFiles = const <File>[],
+ });
final bool success;
final Map<String, ExceptionMeasurement> exceptions;
final Map<String, PerformanceMeasurement> performance;
+ final List<File> inputFiles;
+ final List<File> outputFiles;
bool get hasException => exceptions.isNotEmpty;
}
@@ -472,10 +480,31 @@
// Always persist the file cache to disk.
fileCache.persist();
}
+ // TODO(jonahwilliams): this is a bit of a hack, due to various parts of
+ // the flutter tool writing these files unconditionally. Since Xcode uses
+ // timestamps to track files, this leads to unecessary rebuilds if they
+ // are included. Once all the places that write these files have been
+ // tracked down and moved into assemble, these checks should be removable.
+ {
+ buildInstance.inputFiles.removeWhere((String path, File file) {
+ return path.contains('pubspec.yaml') ||
+ path.contains('.flutter-plugins') ||
+ path.contains('xcconfig');
+ });
+ buildInstance.outputFiles.removeWhere((String path, File file) {
+ return path.contains('pubspec.yaml') ||
+ path.contains('.flutter-plugins') ||
+ path.contains('xcconfig');
+ });
+ }
return BuildResult(
- passed,
- buildInstance.exceptionMeasurements,
- buildInstance.stepTimings,
+ success: passed,
+ exceptions: buildInstance.exceptionMeasurements,
+ performance: buildInstance.stepTimings,
+ inputFiles: buildInstance.inputFiles.values.toList()
+ ..sort((File a, File b) => a.path.compareTo(b.path)),
+ outputFiles: buildInstance.outputFiles.values.toList()
+ ..sort((File a, File b) => a.path.compareTo(b.path)),
);
}
}
@@ -490,6 +519,8 @@
final Map<String, AsyncMemoizer<void>> pending = <String, AsyncMemoizer<void>>{};
final Environment environment;
final FileHashStore fileCache;
+ final Map<String, File> inputFiles = <String, File>{};
+ final Map<String, File> outputFiles = <String, File>{};
// Timings collected during target invocation.
final Map<String, PerformanceMeasurement> stepTimings = <String, PerformanceMeasurement>{};
@@ -514,18 +545,41 @@
try {
final List<File> inputs = target.resolveInputs(environment);
final bool canSkip = await target.computeChanges(inputs, environment, fileCache);
+ for (File input in inputs) {
+ // The build system should produce a list of aggregate input and output
+ // files for the overall build. The goal is to provide this to a hosting
+ // build system, such as Xcode, to configure logic for when to skip the
+ // rule/phase which contains the flutter build. When looking at the
+ // inputs and outputs for the individual rules, we need to be careful to
+ // remove inputs that were actually output from previous build steps.
+ // This indicates that the file is actual an output or intermediary. If
+ // these files are included as both inputs and outputs then it isn't
+ // possible to construct a DAG describing the build.
+ final String resolvedPath = input.resolveSymbolicLinksSync();
+ if (outputFiles.containsKey(resolvedPath)) {
+ continue;
+ }
+ inputFiles[resolvedPath] = input;
+ }
if (canSkip) {
skipped = true;
printStatus('Skipping target: ${target.name}');
+ final List<File> outputs = target.resolveOutputs(environment, implicit: true);
+ for (File output in outputs) {
+ outputFiles[output.resolveSymbolicLinksSync()] = output;
+ }
} else {
printStatus('${target.name}: Starting');
await target.build(inputs, environment);
printStatus('${target.name}: Complete');
- final List<File> outputs = target.resolveOutputs(environment);
+ final List<File> outputs = target.resolveOutputs(environment, implicit: true);
// Update hashes for output files.
await fileCache.hashFiles(outputs);
target._writeStamp(inputs, outputs, environment);
+ for (File output in outputs) {
+ outputFiles[output.resolveSymbolicLinksSync()] = output;
+ }
}
} catch (exception, stackTrace) {
target.clearStamp(environment);
@@ -554,10 +608,10 @@
/// Helper class to collect measurement data.
class PerformanceMeasurement {
- PerformanceMeasurement(this.target, this.elapsedMilliseconds, this.skiped, this.passed);
+ PerformanceMeasurement(this.target, this.elapsedMilliseconds, this.skipped, this.passed);
final int elapsedMilliseconds;
final String target;
- final bool skiped;
+ final bool skipped;
final bool passed;
}
diff --git a/packages/flutter_tools/lib/src/build_system/source.dart b/packages/flutter_tools/lib/src/build_system/source.dart
index a9727e8..6aeda2c 100644
--- a/packages/flutter_tools/lib/src/build_system/source.dart
+++ b/packages/flutter_tools/lib/src/build_system/source.dart
@@ -38,8 +38,10 @@
/// Visit a [Source] which contains a file uri.
///
- /// The uri may that may include constants defined in an [Environment].
- void visitPattern(String pattern) {
+ /// The uri may include constants defined in an [Environment]. If
+ /// [optional] is true, the file is not required to exist. In this case, it
+ /// is never resolved as an input.
+ void visitPattern(String pattern, bool optional) {
// perform substitution of the environmental values and then
// of the local values.
final List<String> segments = <String>[];
@@ -74,38 +76,41 @@
}
rawParts.skip(1).forEach(segments.add);
final String filePath = fs.path.joinAll(segments);
- if (hasWildcard) {
- // Perform a simple match by splitting the wildcard containing file one
- // the `*`. For example, for `/*.dart`, we get [.dart]. We then check
- // that part of the file matches. If there are values before and after
- // the `*` we need to check that both match without overlapping. For
- // example, `foo_*_.dart`. We want to match `foo_b_.dart` but not
- // `foo_.dart`. To do so, we first subtract the first section from the
- // string if the first segment matches.
- final List<String> segments = wildcardFile.split('*');
- if (segments.length > 2) {
- throw InvalidPatternException(pattern);
+ if (!hasWildcard) {
+ if (optional && !fs.isFileSync(filePath)) {
+ return;
}
- if (!fs.directory(filePath).existsSync()) {
- throw Exception('$filePath does not exist!');
- }
- for (FileSystemEntity entity in fs.directory(filePath).listSync()) {
- final String filename = fs.path.basename(entity.path);
- if (segments.isEmpty) {
- sources.add(fs.file(entity.absolute));
- } else if (segments.length == 1) {
- if (filename.startsWith(segments[0]) ||
- filename.endsWith(segments[0])) {
- sources.add(entity.absolute);
- }
- } else if (filename.startsWith(segments[0])) {
- if (filename.substring(segments[0].length).endsWith(segments[1])) {
- sources.add(entity.absolute);
- }
+ sources.add(fs.file(fs.path.normalize(filePath)));
+ return;
+ }
+ // Perform a simple match by splitting the wildcard containing file one
+ // the `*`. For example, for `/*.dart`, we get [.dart]. We then check
+ // that part of the file matches. If there are values before and after
+ // the `*` we need to check that both match without overlapping. For
+ // example, `foo_*_.dart`. We want to match `foo_b_.dart` but not
+ // `foo_.dart`. To do so, we first subtract the first section from the
+ // string if the first segment matches.
+ final List<String> wildcardSegments = wildcardFile.split('*');
+ if (wildcardSegments.length > 2) {
+ throw InvalidPatternException(pattern);
+ }
+ if (!fs.directory(filePath).existsSync()) {
+ throw Exception('$filePath does not exist!');
+ }
+ for (FileSystemEntity entity in fs.directory(filePath).listSync()) {
+ final String filename = fs.path.basename(entity.path);
+ if (wildcardSegments.isEmpty) {
+ sources.add(fs.file(entity.absolute));
+ } else if (wildcardSegments.length == 1) {
+ if (filename.startsWith(wildcardSegments[0]) ||
+ filename.endsWith(wildcardSegments[0])) {
+ sources.add(entity.absolute);
+ }
+ } else if (filename.startsWith(wildcardSegments[0])) {
+ if (filename.substring(wildcardSegments[0].length).endsWith(wildcardSegments[1])) {
+ sources.add(entity.absolute);
}
}
- } else {
- sources.add(fs.file(fs.path.normalize(filePath)));
}
}
@@ -139,7 +144,7 @@
abstract class Source {
/// This source is a file-uri which contains some references to magic
/// environment variables.
- const factory Source.pattern(String pattern) = _PatternSource;
+ const factory Source.pattern(String pattern, { bool optional }) = _PatternSource;
/// This source is produced by invoking the provided function.
const factory Source.function(InputFunction function) = _FunctionSource;
@@ -203,12 +208,13 @@
}
class _PatternSource implements Source {
- const _PatternSource(this.value);
+ const _PatternSource(this.value, { this.optional = false });
final String value;
+ final bool optional;
@override
- void accept(SourceVisitor visitor) => visitor.visitPattern(value);
+ void accept(SourceVisitor visitor) => visitor.visitPattern(value, optional);
@override
bool get implicit => value.contains('*');
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 7b21df8..934fc0f 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
@@ -7,6 +7,8 @@
import '../../asset.dart';
import '../../base/file_system.dart';
import '../../devfs.dart';
+import '../../plugins.dart';
+import '../../project.dart';
import '../build_system.dart';
/// The copying logic for flutter assets.
@@ -100,3 +102,45 @@
}));
}
}
+
+/// Rewrites the `.flutter-plugins` file of [project] based on the plugin
+/// dependencies declared in `pubspec.yaml`.
+// TODO(jonahwiliams): this should be per platform and located in build
+// outputs.
+class FlutterPlugins extends Target {
+ const FlutterPlugins();
+
+ @override
+ String get name => 'flutter_plugins';
+
+ @override
+ List<Target> get dependencies => const <Target>[];
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/assets.dart'),
+ Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{PROJECT_DIR}/.flutter-plugins')
+ ];
+
+ @override
+ Future<void> build(List<File> inputFiles, Environment environment) async {
+ // The pubspec may change for reasons other than plugins changing, so we compare
+ // the manifest before writing. Some hosting build systems use timestamps
+ // so we need to be careful to avoid tricking them into doing more work than
+ // necessary.
+ final FlutterProject project = FlutterProject.fromDirectory(environment.projectDir);
+ final List<Plugin> plugins = findPlugins(project);
+ final String pluginManifest = plugins
+ .map<String>((Plugin p) => '${p.name}=${escapePath(p.path)}')
+ .join('\n');
+ final File flutterPluginsFile = environment.projectDir.childFile('.flutter-plugins');
+ if (!flutterPluginsFile.existsSync() || flutterPluginsFile.readAsStringSync() != pluginManifest) {
+ flutterPluginsFile.writeAsStringSync(pluginManifest);
+ }
+ }
+}
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 6b7be2e..2c2d4d7 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -86,6 +86,9 @@
}
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final String targetFile = environment.defines[kTargetFile] ?? fs.path.join('lib', 'main.dart');
+ final String packagesPath = environment.projectDir.childFile('.packages').path;
+ final PackageUriMapper packageUriMapper = PackageUriMapper(targetFile,
+ packagesPath, null, null);
final CompilerOutput output = await compiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
@@ -95,7 +98,8 @@
targetProductVm: buildMode == BuildMode.release,
outputFilePath: environment.buildDir.childFile('app.dill').path,
depFilePath: null,
- mainPath: targetFile,
+ packagesPath: packagesPath,
+ mainPath: packageUriMapper.map(targetFile)?.toString() ?? targetFile,
);
if (output.errorCount != 0) {
throw Exception('Errors during snapshot creation: $output');
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 3d14ca7..95d5edf 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
@@ -6,10 +6,16 @@
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../base/process_manager.dart';
+import '../../build_info.dart';
import '../../globals.dart';
+import '../../macos/cocoapods.dart';
+import '../../project.dart';
import '../build_system.dart';
+import '../exceptions.dart';
+import 'assets.dart';
+import 'dart.dart';
-const String _kOutputPrefix = '{PROJECT_DIR}/macos/Flutter/FlutterMacOS.framework';
+const String _kOutputPrefix = '{PROJECT_DIR}/macos/Flutter/ephemeral/FlutterMacOS.framework';
/// Copy the macOS framework to the correct copy dir by invoking 'cp -R'.
///
@@ -63,6 +69,7 @@
.projectDir
.childDirectory('macos')
.childDirectory('Flutter')
+ .childDirectory('ephemeral')
.childDirectory('FlutterMacOS.framework');
if (targetDirectory.existsSync()) {
targetDirectory.deleteSync(recursive: true);
@@ -78,3 +85,112 @@
}
}
}
+
+/// Tell cocoapods to re-fetch dependencies.
+class DebugMacOSPodInstall extends Target {
+ const DebugMacOSPodInstall();
+
+ @override
+ String get name => 'debug_macos_pod_install';
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.artifact(Artifact.flutterMacOSPodspec,
+ platform: TargetPlatform.darwin_x64,
+ mode: BuildMode.debug
+ ),
+ Source.pattern('{PROJECT_DIR}/macos/Podfile', optional: true),
+ Source.pattern('{PROJECT_DIR}/macos/Runner.xcodeproj/project.pbxproj'),
+ Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/Flutter-Generated.xcconfig'),
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ // TODO(jonahwilliams): introduce configuration/planning phase to build.
+ // No outputs because Cocoapods is fully responsible for tracking. plus there
+ // is no concept of an optional output. Instead we will need a build config
+ // phase to conditionally add this rule so that it can be written properly.
+ ];
+
+ @override
+ List<Target> get dependencies => const <Target>[
+ UnpackMacOS(),
+ FlutterPlugins(),
+ ];
+
+ @override
+ Future<void> build(List<File> inputFiles, Environment environment) async {
+ if (environment.defines[kBuildMode] == null) {
+ throw MissingDefineException(kBuildMode, 'debug_macos_pod_install');
+ }
+ // If there is no podfile do not perform any pods actions.
+ if (!environment.projectDir.childDirectory('macos')
+ .childFile('Podfile').existsSync()) {
+ return;
+ }
+ final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
+ final FlutterProject project = FlutterProject.fromDirectory(environment.projectDir);
+ final String enginePath = artifacts.getArtifactPath(Artifact.flutterMacOSPodspec,
+ mode: buildMode, platform: TargetPlatform.darwin_x64);
+
+ await cocoaPods.processPods(
+ xcodeProject: project.macos,
+ engineDir: enginePath,
+ isSwift: true,
+ dependenciesChanged: true,
+ );
+ }
+}
+
+/// Build all of the artifacts for a debug macOS application.
+class DebugMacOSApplication extends Target {
+ const DebugMacOSApplication();
+
+ @override
+ Future<void> build(List<File> inputFiles, Environment environment) async {
+ final File sourceFile = environment.buildDir.childFile('app.dill');
+ final File destinationFile = environment.buildDir
+ .childDirectory('flutter_assets')
+ .childFile('kernel_blob.bin');
+ if (!destinationFile.parent.existsSync()) {
+ destinationFile.parent.createSync(recursive: true);
+ }
+ sourceFile.copySync(destinationFile.path);
+ }
+
+ @override
+ List<Target> get dependencies => const <Target>[
+ FlutterPlugins(),
+ UnpackMacOS(),
+ KernelSnapshot(),
+ CopyAssets(),
+ DebugMacOSPodInstall(),
+ ];
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.pattern('{BUILD_DIR}/app.dill')
+ ];
+
+ @override
+ String get name => 'debug_macos_application';
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{BUILD_DIR}/flutter_assets/kernel_blob.bin'),
+ ];
+}
+
+// TODO(jonahwilliams): real AOT implementation.
+class ReleaseMacOSApplication extends DebugMacOSApplication {
+ const ReleaseMacOSApplication();
+
+ @override
+ String get name => 'release_macos_application';
+}
+class ProfileMacOSApplication extends DebugMacOSApplication {
+ const ProfileMacOSApplication();
+
+ @override
+ String get name => 'profile_macos_application';
+}
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 78cb1ee..476d788 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:meta/meta.dart';
+
import '../base/common.dart';
import '../base/context.dart';
+import '../base/file_system.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/assets.dart';
import '../build_system/targets/dart.dart';
@@ -29,6 +32,9 @@
AotElfRelease(),
AotAssemblyProfile(),
AotAssemblyRelease(),
+ DebugMacOSApplication(),
+ ProfileMacOSApplication(),
+ ReleaseMacOSApplication(),
];
/// Assemble provides a low level API to interact with the flutter tool build
@@ -40,14 +46,14 @@
abbr: 'd',
help: 'Allows passing configuration to a target with --define=target=key=value.'
);
- argParser.addOption(
- 'build-mode',
- allowed: const <String>[
- 'debug',
- 'profile',
- 'release',
- ],
- );
+ argParser.addOption('build-inputs', help: 'A file path where a newline '
+ 'separated file containing all inputs used will be written after a build.'
+ ' This file is not included as a build input or output. This file is not'
+ ' written if the build fails for any reason.');
+ argParser.addOption('build-outputs', help: 'A file path where a newline '
+ 'separated file containing all outputs used will be written after a build.'
+ ' This file is not included as a build input or output. This file is not'
+ ' written if the build fails for any reason.');
argParser.addOption(
'resource-pool-size',
help: 'The maximum number of concurrent tasks the build system will run.'
@@ -106,10 +112,33 @@
printError('Target ${data.key} failed: ${data.value.exception}');
printError('${data.value.exception}');
}
- throwToolExit('build failed');
- } else {
- printStatus('build succeeded');
+ throwToolExit('build failed.');
+ }
+ printStatus('build succeeded.');
+ if (argResults.wasParsed('build-inputs')) {
+ writeListIfChanged(result.inputFiles, argResults['build-inputs']);
+ }
+ if (argResults.wasParsed('build-outputs')) {
+ writeListIfChanged(result.outputFiles, argResults['build-outputs']);
}
return null;
}
}
+
+@visibleForTesting
+void writeListIfChanged(List<File> files, String path) {
+ final File file = fs.file(path);
+ final StringBuffer buffer = StringBuffer();
+ // These files are already sorted.
+ for (File file in files) {
+ buffer.writeln(file.resolveSymbolicLinksSync());
+ }
+ final String newContents = buffer.toString();
+ if (!file.existsSync()) {
+ file.writeAsStringSync(newContents);
+ }
+ final String currentContents = file.readAsStringSync();
+ if (currentContents != newContents) {
+ file.writeAsStringSync(newContents);
+ }
+}
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 32521d7..a9b1caf 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -257,7 +257,6 @@
device,
flutterProject: flutterProject,
trackWidgetCreation: argResults['track-widget-creation'],
- dillOutputPath: argResults['output-dill'],
fileSystemRoots: argResults['filesystem-root'],
fileSystemScheme: argResults['filesystem-scheme'],
viewFilter: argResults['isolate-filter'],
diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart
index f871186..dc27a17 100644
--- a/packages/flutter_tools/lib/src/commands/build_macos.dart
+++ b/packages/flutter_tools/lib/src/commands/build_macos.dart
@@ -14,7 +14,7 @@
import '../runner/flutter_command.dart' show FlutterCommandResult;
import 'build.dart';
-/// A command to build a macos desktop target through a build shell script.
+/// A command to build a macOS desktop target through a build shell script.
class BuildMacosCommand extends BuildSubCommand {
BuildMacosCommand() {
usesTargetOption();
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index a441ecf..e1bf5ec 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -406,7 +406,6 @@
device,
flutterProject: flutterProject,
trackWidgetCreation: trackWidgetCreation,
- dillOutputPath: dillOutputPath,
viewFilter: isolateFilter,
target: target,
buildMode: options.buildInfo.mode,
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 298386c..6e9d869 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -397,7 +397,6 @@
device,
flutterProject: flutterProject,
trackWidgetCreation: argResults['track-widget-creation'],
- dillOutputPath: argResults['output-dill'],
fileSystemRoots: argResults['filesystem-root'],
fileSystemScheme: argResults['filesystem-scheme'],
viewFilter: argResults['isolate-filter'],
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index af96308..bd9b6d1 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -48,13 +48,15 @@
String targetOverride,
bool useMacOSConfig = false,
bool setSymroot = true,
+ String buildDirOverride,
}) async {
final List<String> xcodeBuildSettings = _xcodeBuildSettingsLines(
project: project,
buildInfo: buildInfo,
targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig,
- setSymroot: setSymroot
+ setSymroot: setSymroot,
+ buildDirOverride: buildDirOverride
);
_updateGeneratedXcodePropertiesFile(
@@ -121,6 +123,7 @@
String targetOverride,
bool useMacOSConfig = false,
bool setSymroot = true,
+ String buildDirOverride,
}) {
final List<String> xcodeBuildSettings = <String>[];
@@ -135,7 +138,7 @@
xcodeBuildSettings.add('FLUTTER_TARGET=$targetOverride');
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
- xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${getBuildDirectory()}');
+ xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');
if (setSymroot) {
xcodeBuildSettings.add('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
diff --git a/packages/flutter_tools/lib/src/macos/application_package.dart b/packages/flutter_tools/lib/src/macos/application_package.dart
index cfa4abc..000bb7f 100644
--- a/packages/flutter_tools/lib/src/macos/application_package.dart
+++ b/packages/flutter_tools/lib/src/macos/application_package.dart
@@ -6,7 +6,6 @@
import '../application_package.dart';
import '../base/file_system.dart';
-import '../build_info.dart';
import '../globals.dart';
import '../ios/plist_utils.dart' as plist;
import '../project.dart';
@@ -31,18 +30,17 @@
/// which is expected to start the application and send the observatory
/// port over stdout.
factory MacOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
- final _ExecutableAndId executableAndId = _executableFromBundle(applicationBinary);
+ final ExecutableAndId executableAndId = executableFromBundle(applicationBinary);
final Directory applicationBundle = fs.directory(applicationBinary);
return PrebuiltMacOSApp(
bundleDir: applicationBundle,
bundleName: applicationBundle.path,
- projectBundleId: executableAndId.id,
- executable: executableAndId.executable,
+ executableAndId: executableAndId,
);
}
/// Look up the executable name for a macOS application bundle.
- static _ExecutableAndId _executableFromBundle(Directory applicationBundle) {
+ static ExecutableAndId executableFromBundle(Directory applicationBundle) {
final FileSystemEntityType entityType = fs.typeSync(applicationBundle.path);
if (entityType == FileSystemEntityType.notFound) {
printError('File "${applicationBundle.path}" does not exist.');
@@ -75,40 +73,28 @@
if (!fs.file(executable).existsSync()) {
printError('Could not find macOS binary at $executable');
}
- return _ExecutableAndId(executable, id);
+ return ExecutableAndId(executable, id);
}
@override
String get displayName => id;
-
- String applicationBundle(BuildMode buildMode);
-
- String executable(BuildMode buildMode);
}
class PrebuiltMacOSApp extends MacOSApp {
PrebuiltMacOSApp({
@required this.bundleDir,
@required this.bundleName,
- @required this.projectBundleId,
- @required String executable,
- }) : _executable = executable,
- super(projectBundleId: projectBundleId);
+ @required this.executableAndId,
+ });
final Directory bundleDir;
final String bundleName;
- final String projectBundleId;
-
- final String _executable;
+ final ExecutableAndId executableAndId;
@override
String get name => bundleName;
- @override
- String applicationBundle(BuildMode buildMode) => bundleDir.path;
-
- @override
- String executable(BuildMode buildMode) => _executable;
+ String get executable => executableAndId.executable;
}
class BuildableMacOSApp extends MacOSApp {
@@ -118,35 +104,10 @@
@override
String get name => 'macOS';
-
- @override
- String applicationBundle(BuildMode buildMode) {
- final File appBundleNameFile = project.nameFile;
- if (!appBundleNameFile.existsSync()) {
- printError('Unable to find app name. ${appBundleNameFile.path} does not exist');
- return null;
- }
- return fs.path.join(
- getMacOSBuildDirectory(),
- 'Build',
- 'Products',
- buildMode == BuildMode.debug ? 'Debug' : 'Release',
- appBundleNameFile.readAsStringSync().trim());
- }
-
- @override
- String executable(BuildMode buildMode) {
- final String directory = applicationBundle(buildMode);
- if (directory == null) {
- return null;
- }
- final _ExecutableAndId executableAndId = MacOSApp._executableFromBundle(fs.directory(directory));
- return executableAndId.executable;
- }
}
-class _ExecutableAndId {
- _ExecutableAndId(this.executable, this.id);
+class ExecutableAndId {
+ ExecutableAndId(this.executable, this.id);
final String executable;
final String id;
diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart
index 2b45525..a72a8b8 100644
--- a/packages/flutter_tools/lib/src/macos/build_macos.dart
+++ b/packages/flutter_tools/lib/src/macos/build_macos.dart
@@ -8,25 +8,34 @@
import '../base/logger.dart';
import '../base/process_manager.dart';
import '../build_info.dart';
+import '../build_system/build_system.dart';
+import '../build_system/targets/dart.dart';
import '../convert.dart';
import '../globals.dart';
import '../ios/xcodeproj.dart';
import '../project.dart';
import '../reporting/reporting.dart';
+import 'application_package.dart';
-import 'cocoapod_utils.dart';
-
-/// Builds the macOS project through xcodebuild.
-// TODO(jonahwilliams): refactor to share code with the existing iOS code.
-Future<void> buildMacOS({
+/// Builds the macOS project through xcodebuild and returns the app bundle.
+Future<PrebuiltMacOSApp> buildMacOS({
FlutterProject flutterProject,
BuildInfo buildInfo,
- String targetOverride,
+ String targetOverride = 'lib/main.dart',
}) async {
- final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
- if (!flutterBuildDir.existsSync()) {
- flutterBuildDir.createSync(recursive: true);
- }
+ // Create the environment used to process the build. This needs to match what
+ // is provided in bin/macos_build_flutter_assets.sh otherwise the directories
+ // will be different.
+ final Environment environment = Environment(
+ projectDir: flutterProject.directory,
+ buildDir: flutterProject.dartTool.childDirectory('flutter_build'),
+ defines: <String, String>{
+ kBuildMode: buildInfo.isDebug == true ? 'debug' : 'release',
+ kTargetPlatform: 'darwin-x64',
+ kTargetFile: fs.file(targetOverride).absolute.path
+ },
+ );
+
// Write configuration to an xconfig file in a standard location.
await updateGeneratedXcodeProperties(
project: flutterProject,
@@ -34,27 +43,34 @@
targetOverride: targetOverride,
useMacOSConfig: true,
setSymroot: false,
+ buildDirOverride: environment.buildDir.path,
);
- await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
-
+ // If the xcfilelists do not exist, create empty version.
+ if (!flutterProject.macos.inputFileList.existsSync()) {
+ flutterProject.macos.inputFileList.createSync(recursive: true);
+ }
+ if (!flutterProject.macos.outputFileList.existsSync()) {
+ flutterProject.macos.outputFileList.createSync(recursive: true);
+ }
// Set debug or release mode.
String config = 'Debug';
- if (buildInfo.isRelease) {
+ if (buildInfo.isRelease ?? false) {
config = 'Release';
}
- // Run build script provided by application.
+ // Invoke Xcode with correct configuration.
final Stopwatch sw = Stopwatch()..start();
- final Process process = await processManager.start(<String>[
+ final List<String> command = <String>[
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-workspace', flutterProject.macos.xcodeWorkspace.path,
- '-configuration', '$config',
+ '-configuration', config,
'-scheme', 'Runner',
- '-derivedDataPath', flutterBuildDir.absolute.path,
- 'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
- 'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
- ], runInShell: true);
+ '-derivedDataPath', environment.buildDir.path,
+ 'OBJROOT=${fs.path.join(environment.buildDir.path, 'Build', 'Intermediates.noindex')}',
+ 'SYMROOT=${fs.path.join(environment.buildDir.path, 'Build', 'Products')}',
+ ];
+ final Process process = await processManager.start(command);
final Status status = logger.startProgress(
'Building macOS application...',
timeout: null,
@@ -77,4 +93,13 @@
throwToolExit('Build process failed');
}
flutterUsage.sendTiming('build', 'xcode-macos', Duration(milliseconds: sw.elapsedMilliseconds));
+ final File appBundleNameFile = flutterProject.macos.nameFile;
+ final Directory bundleDir = fs.directory(fs.path.join(
+ environment.buildDir.path,
+ 'Build',
+ 'Products',
+ buildInfo.mode == BuildMode.debug ? 'Debug' : 'Release',
+ appBundleNameFile.readAsStringSync().trim(),
+ ));
+ return MacOSApp.fromPrebuiltApp(bundleDir);
}
diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart
index 27ca0bb..fb37cb6 100644
--- a/packages/flutter_tools/lib/src/macos/macos_device.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_device.dart
@@ -80,10 +80,13 @@
bool usesTerminalUi = true,
bool ipv6 = false,
}) async {
+ Cache.releaseLockEarly();
// Stop any running applications with the same executable.
- if (!prebuiltApplication) {
- Cache.releaseLockEarly();
- await buildMacOS(
+ PrebuiltMacOSApp prebuiltMacOSApp;
+ if (prebuiltApplication) {
+ prebuiltMacOSApp = package;
+ } else {
+ prebuiltMacOSApp = await buildMacOS(
flutterProject: FlutterProject.current(),
buildInfo: debuggingOptions?.buildInfo,
targetOverride: mainPath,
@@ -91,8 +94,7 @@
}
// Ensure that the executable is locatable.
- final String executable = package.executable(debuggingOptions?.buildInfo?.mode);
- if (executable == null) {
+ if (prebuiltMacOSApp == null) {
printError('Unable to find executable to run');
return LaunchResult.failed();
}
@@ -100,7 +102,7 @@
// Make sure to call stop app after we've built.
await stopApp(package);
final Process process = await processManager.start(<String>[
- executable
+ prebuiltMacOSApp.executable,
]);
if (debuggingOptions?.buildInfo?.isRelease == true) {
return LaunchResult.succeeded();
@@ -111,7 +113,7 @@
final Uri observatoryUri = await observatoryDiscovery.uri;
// Bring app to foreground.
await processManager.run(<String>[
- 'open', package.applicationBundle(debuggingOptions?.buildInfo?.mode),
+ 'open', prebuiltMacOSApp.bundleName,
]);
return LaunchResult.succeeded(observatoryUri: observatoryUri);
} catch (error) {
@@ -125,9 +127,8 @@
// TODO(jonahwilliams): implement using process manager.
// currently we rely on killing the isolate taking down the application.
@override
- Future<bool> stopApp(covariant MacOSApp app) async {
- // Assume debug for now.
- return killProcess(app.executable(BuildMode.debug));
+ Future<bool> stopApp(covariant PrebuiltMacOSApp app) async {
+ return killProcess(app.executable);
}
@override
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index d9d7a3e..513dcee 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -664,6 +664,14 @@
/// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
+ /// The xcfilelist used to track the inputs for the Flutter script phase in
+ /// the Xcode build.
+ File get inputFileList => ephemeralDirectory.childFile('FlutterInputs.xcfilelist');
+
+ /// The xcfilelist used to track the outputs for the Flutter script phase in
+ /// the Xcode build.
+ File get outputFileList => ephemeralDirectory.childFile('FlutterOutputs.xcfilelist');
+
@override
File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig');
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index da4cbe9..88ab5dc 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -31,7 +31,6 @@
FlutterDevice(
this.device, {
@required this.trackWidgetCreation,
- this.dillOutputPath,
this.fileSystemRoots,
this.fileSystemScheme,
this.viewFilter,
@@ -56,7 +55,6 @@
@required bool trackWidgetCreation,
@required String target,
@required BuildMode buildMode,
- String dillOutputPath,
List<String> fileSystemRoots,
String fileSystemScheme,
String viewFilter,
@@ -82,7 +80,6 @@
return FlutterDevice(
device,
trackWidgetCreation: trackWidgetCreation,
- dillOutputPath: dillOutputPath,
fileSystemRoots: fileSystemRoots,
fileSystemScheme:fileSystemScheme,
viewFilter: viewFilter,
@@ -99,7 +96,6 @@
List<VMService> vmServices;
DevFS devFS;
ApplicationPackage package;
- String dillOutputPath;
List<String> fileSystemRoots;
String fileSystemScheme;
StreamSubscription<String> _loggingSubscription;
@@ -476,6 +472,7 @@
bool fullRestart = false,
String projectRootPath,
String pathToReload,
+ @required String dillOutputPath,
@required List<Uri> invalidatedFiles,
}) async {
final Status devFSStatus = logger.startProgress(
@@ -527,12 +524,25 @@
this.usesTerminalUi = true,
this.stayResident = true,
this.hotMode = true,
+ this.dillOutputPath,
}) {
_mainPath = findMainDartFile(target);
_projectRootPath = projectRootPath ?? fs.currentDirectory.path;
_packagesFilePath =
packagesFilePath ?? fs.path.absolute(PackageMap.globalPackagesPath);
_assetBundle = AssetBundleFactory.instance.createBundle();
+ // TODO(jonahwilliams): this is transitionary logic to allow us to support
+ // platforms that are not yet using flutter assemble. In the "new world",
+ // builds are isolated based on a number of factors. Thus, we cannot assume
+ // that a debug build will create the expected `build/app.dill` file. For
+ // now, I'm working around this by just creating it if it is missing here.
+ // In the future, once build & run are more strongly separated, the build
+ // environment will be plumbed through so that it all comes from a single
+ // source of truth, the [Environment].
+ final File dillOutput = fs.file(dillOutputPath ?? fs.path.join('build', 'app.dill'));
+ if (!dillOutput.existsSync()) {
+ dillOutput.createSync(recursive: true);
+ }
}
final List<FlutterDevice> flutterDevices;
@@ -542,6 +552,7 @@
final bool stayResident;
final bool ipv6;
final Completer<int> _finished = Completer<int>();
+ final String dillOutputPath;
bool _exited = false;
bool hotMode ;
String _packagesFilePath;
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 08a4956..7a8105d 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -63,7 +63,7 @@
this.hostIsIde = false,
String projectRootPath,
String packagesFilePath,
- this.dillOutputPath,
+ String dillOutputPath,
bool stayResident = true,
bool ipv6 = false,
}) : super(devices,
@@ -74,13 +74,13 @@
packagesFilePath: packagesFilePath,
stayResident: stayResident,
hotMode: true,
+ dillOutputPath: dillOutputPath,
ipv6: ipv6);
final bool benchmarkMode;
final File applicationBinary;
final bool hostIsIde;
bool _didAttach = false;
- final String dillOutputPath;
final Map<String, List<int>> benchmarkData = <String, List<int>>{};
// The initial launch is from a snapshot.
@@ -304,6 +304,7 @@
projectRootPath: projectRootPath,
pathToReload: getReloadPath(fullRestart: fullRestart),
invalidatedFiles: invalidatedFiles,
+ dillOutputPath: dillOutputPath,
));
}
return results;
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 ca49ef0..e806251 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
@@ -138,6 +138,14 @@
expect(stampContents['inputs'], <Object>['/foo.dart']);
}));
+ test('Creates a BuildResult with inputs and outputs', () => testbed.run(() async {
+ final BuildResult result = await buildSystem.build(fooTarget, environment);
+
+ expect(result.inputFiles.single.path, fs.path.absolute('foo.dart'));
+ expect(result.outputFiles.single.path,
+ fs.path.absolute(fs.path.join(environment.buildDir.path, 'out')));
+ }));
+
test('Does not re-invoke build if stamp is valid', () => testbed.run(() async {
await buildSystem.build(fooTarget, environment);
await buildSystem.build(fooTarget, environment);
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
index d49b2e3..8d0c2a4 100644
--- a/packages/flutter_tools/test/general.shard/build_system/source_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/source_test.dart
@@ -135,6 +135,14 @@
expect(() => invalidBase.accept(visitor), throwsA(isInstanceOf<InvalidPatternException>()));
}));
+
+ test('can substitute optional files', () => testbed.run(() {
+ const Source missingSource = Source.pattern('{PROJECT_DIR}/foo', optional: true);
+
+ expect(fs.file('foo').existsSync(), false);
+ missingSource.accept(visitor);
+ expect(visitor.sources, isEmpty);
+ }));
}
class TestBehavior extends SourceBehavior {
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 2541115..f4fbb50 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
@@ -65,4 +65,15 @@
// 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('FlutterPlugins updates required files as needed', () => testbed.run(() async {
+ fs.file('pubspec.yaml')
+ ..writeAsStringSync('name: foo\ndependencies:\n foo: any\n');
+
+ await const FlutterPlugins().build(<File>[], Environment(
+ projectDir: fs.currentDirectory,
+ ));
+
+ expect(fs.file('.flutter-plugins').existsSync(), true);
+ }));
}
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 554f147..b6e2f76 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
@@ -7,8 +7,11 @@
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/exceptions.dart';
+import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/build_system/targets/macos.dart';
import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/macos/cocoapods.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
@@ -17,7 +20,6 @@
void main() {
Testbed testbed;
- const BuildSystem buildSystem = BuildSystem();
Environment environment;
MockPlatform mockPlatform;
@@ -31,6 +33,7 @@
when(mockPlatform.isWindows).thenReturn(false);
when(mockPlatform.isMacOS).thenReturn(true);
when(mockPlatform.isLinux).thenReturn(false);
+ when(mockPlatform.environment).thenReturn(const <String, String>{});
testbed = Testbed(setup: () {
environment = Environment(
projectDir: fs.currentDirectory,
@@ -79,28 +82,117 @@
});
test('Copies files to correct cache directory', () => testbed.run(() async {
- await buildSystem.build(const UnpackMacOS(), environment);
+ await const UnpackMacOS().build(<File>[], 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);
+ expect(fs.directory('macos/Flutter/ephemeral/FlutterMacOS.framework').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/FlutterMacOS').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FLEOpenGLContextHandling.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FLEReshapeListener.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FLEView.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FLEViewController.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterBinaryMessenger.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterChannels.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterCodecs.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterMacOS.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterPluginMacOS.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Headers/FlutterPluginRegisrarMacOS.h').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Modules/module.modulemap').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Resources/icudtl.dat').existsSync(), true);
+ expect(fs.file('macos/Flutter/ephemeral/FlutterMacOS.framework/Resources/info.plist').existsSync(), true);
}));
+
+ test('debug macOS application copies kernel blob', () => testbed.run(() async {
+ final String inputKernel = fs.path.join(environment.buildDir.path, 'app.dill');
+ final String outputKernel = fs.path.join(environment.buildDir.path, 'flutter_assets', 'kernel_blob.bin');
+ fs.file(inputKernel)
+ ..createSync(recursive: true)
+ ..writeAsStringSync('testing');
+
+ await const DebugMacOSApplication().build(<File>[], environment);
+
+ expect(fs.file(outputKernel).readAsStringSync(), 'testing');
+ }));
+
+ test('profile macOS application copies kernel blob', () => testbed.run(() async {
+ final String inputKernel = fs.path.join(environment.buildDir.path, 'app.dill');
+ final String outputKernel = fs.path.join(environment.buildDir.path, 'flutter_assets', 'kernel_blob.bin');
+ fs.file(inputKernel)
+ ..createSync(recursive: true)
+ ..writeAsStringSync('testing');
+
+ await const ProfileMacOSApplication().build(<File>[], environment);
+
+ expect(fs.file(outputKernel).readAsStringSync(), 'testing');
+ }));
+
+ test('release macOS application copies kernel blob', () => testbed.run(() async {
+ final String inputKernel = fs.path.join(environment.buildDir.path, 'app.dill');
+ final String outputKernel = fs.path.join(environment.buildDir.path, 'flutter_assets', 'kernel_blob.bin');
+ fs.file(inputKernel)
+ ..createSync(recursive: true)
+ ..writeAsStringSync('testing');
+
+ await const ReleaseMacOSApplication().build(<File>[], environment);
+
+ expect(fs.file(outputKernel).readAsStringSync(), 'testing');
+ }));
+
+ // Changing target names will require a corresponding update in flutter_tools/bin/macos_build_flutter_assets.sh.
+ test('Target names match those expected by bin scripts', () => testbed.run(() async {
+ expect(const DebugMacOSApplication().name, 'debug_macos_application');
+ expect(const ProfileMacOSApplication().name, 'profile_macos_application');
+ expect(const ReleaseMacOSApplication().name, 'release_macos_application');
+ }));
+
+
+ test('DebugMacOSPodInstall throws if missing build mode', () => testbed.run(() async {
+ expect(() => const DebugMacOSPodInstall().build(<File>[], environment),
+ throwsA(isInstanceOf<MissingDefineException>()));
+ }));
+
+ test('DebugMacOSPodInstall skips if podfile does not exist', () => testbed.run(() async {
+ await const DebugMacOSPodInstall().build(<File>[], Environment(
+ projectDir: fs.currentDirectory,
+ defines: <String, String>{
+ kBuildMode: 'debug'
+ }
+ ));
+
+ verifyNever(cocoaPods.processPods(
+ xcodeProject: anyNamed('xcodeProject'),
+ engineDir: anyNamed('engineDir'),
+ isSwift: true,
+ dependenciesChanged: true));
+ }, overrides: <Type, Generator>{
+ CocoaPods: () => MockCocoaPods(),
+ }));
+
+ test('DebugMacOSPodInstall invokes processPods with podfile', () => testbed.run(() async {
+ fs.file(fs.path.join('macos', 'Podfile')).createSync(recursive: true);
+ await const DebugMacOSPodInstall().build(<File>[], Environment(
+ projectDir: fs.currentDirectory,
+ defines: <String, String>{
+ kBuildMode: 'debug'
+ }
+ ));
+
+ verify(cocoaPods.processPods(
+ xcodeProject: anyNamed('xcodeProject'),
+ engineDir: anyNamed('engineDir'),
+ isSwift: true,
+ dependenciesChanged: true)).called(1);
+ }, overrides: <Type, Generator>{
+ CocoaPods: () => MockCocoaPods(),
+ }));
+
+ test('b', () => testbed.run(() async {
+
+ }));
+
}
class MockPlatform extends Mock implements Platform {}
-
+class MockCocoaPods extends Mock implements CocoaPods {}
class MockProcessManager extends Mock implements ProcessManager {}
class FakeProcessResult implements ProcessResult {
@override
@@ -115,3 +207,5 @@
@override
String stdout = '';
}
+
+
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 61ea1a8..638582b 100644
--- a/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/assemble_test.dart
@@ -3,6 +3,7 @@
// 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';
@@ -31,13 +32,54 @@
test('Can run a build', () => testbed.run(() async {
when(mockBuildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async {
- return BuildResult(true, const <String, ExceptionMeasurement>{}, const <String, PerformanceMeasurement>{});
+ return BuildResult(success: true);
});
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', 'unpack_macos']);
final BufferLogger bufferLogger = logger;
- expect(bufferLogger.statusText.trim(), 'build succeeded');
+ expect(bufferLogger.statusText.trim(), 'build succeeded.');
+ }));
+
+ test('Only writes input and output files when the values change', () => testbed.run(() async {
+ when(mockBuildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
+ .thenAnswer((Invocation invocation) async {
+ return BuildResult(
+ success: true,
+ inputFiles: <File>[fs.file('foo')..createSync()],
+ outputFiles: <File>[fs.file('bar')..createSync()],
+ );
+ });
+
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+ await commandRunner.run(<String>['assemble', '--build-outputs=outputs', '--build-inputs=inputs', 'unpack_macos']);
+
+ final File inputs = fs.file('inputs');
+ final File outputs = fs.file('outputs');
+ expect(inputs.readAsStringSync(), contains('foo'));
+ expect(outputs.readAsStringSync(), contains('bar'));
+
+ final DateTime theDistantPast = DateTime(1991, 8, 23);
+ inputs.setLastModifiedSync(theDistantPast);
+ outputs.setLastModifiedSync(theDistantPast);
+ await commandRunner.run(<String>['assemble', '--build-outputs=outputs', '--build-inputs=inputs', 'unpack_macos']);
+
+ expect(inputs.lastModifiedSync(), theDistantPast);
+ expect(outputs.lastModifiedSync(), theDistantPast);
+
+
+ when(mockBuildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
+ .thenAnswer((Invocation invocation) async {
+ return BuildResult(
+ success: true,
+ inputFiles: <File>[fs.file('foo'), fs.file('fizz')..createSync()],
+ outputFiles: <File>[fs.file('bar')]);
+ });
+ await commandRunner.run(<String>['assemble', '--build-outputs=outputs', '--build-inputs=inputs', 'unpack_macos']);
+
+ expect(inputs.readAsStringSync(), contains('foo'));
+ expect(inputs.readAsStringSync(), contains('fizz'));
+ expect(inputs.lastModifiedSync(), isNot(theDistantPast));
}));
}
diff --git a/packages/flutter_tools/test/general.shard/commands/attach_test.dart b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
index c708f1e..bfe29a9 100644
--- a/packages/flutter_tools/test/general.shard/commands/attach_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/attach_test.dart
@@ -168,7 +168,6 @@
// output dill, filesystem scheme, and filesystem root.
final FlutterDevice flutterDevice = flutterDevices.first;
- expect(flutterDevice.dillOutputPath, outputDill);
expect(flutterDevice.fileSystemScheme, filesystemScheme);
expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]);
}, overrides: <Type, Generator>{
diff --git a/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
index 462e2d8..63f76c6 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_macos_test.dart
@@ -8,7 +8,8 @@
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
-import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/build_system/build_system.dart';
+import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/features.dart';
@@ -86,25 +87,35 @@
fs.file('.packages').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
final FlutterProject flutterProject = FlutterProject.fromDirectory(fs.currentDirectory);
- final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
-
+ final Environment environment = Environment(
+ projectDir: flutterProject.directory,
+ buildDir: flutterProject.dartTool.childDirectory('flutter_build'),
+ defines: <String, String>{
+ kBuildMode: 'release',
+ kTargetFile: fs.path.absolute(fs.path.join('lib', 'main.dart')),
+ kTargetPlatform: 'darwin-x64',
+ }
+ );
when(mockProcessManager.start(<String>[
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-workspace', flutterProject.macos.xcodeWorkspace.path,
- '-configuration', 'Debug',
+ '-configuration', 'Release',
'-scheme', 'Runner',
- '-derivedDataPath', flutterBuildDir.absolute.path,
- 'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
- 'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
- ], runInShell: true)).thenAnswer((Invocation invocation) async {
+ '-derivedDataPath', environment.buildDir.path,
+ 'OBJROOT=${fs.path.join(environment.buildDir.path, 'Build', 'Intermediates.noindex')}',
+ 'SYMROOT=${fs.path.join(environment.buildDir.path, 'Build', 'Products')}',
+ ])).thenAnswer((Invocation invocation) async {
+ fs.file(fs.path.join('macos', 'Flutter', 'ephemeral', '.app_filename'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('example.app');
return mockProcess;
});
- await createTestCommandRunner(command).run(
- const <String>['build', 'macos']
- );
+ expect(createTestCommandRunner(command).run(
+ const <String>['build', 'macos', '--release']
+ ), throwsA(isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
diff --git a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart
index 2dcb59ed..d0d7c3b 100644
--- a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart
@@ -36,7 +36,6 @@
testUsingContext('defaults', () async {
final MockMacOSApp mockMacOSApp = MockMacOSApp();
- when(mockMacOSApp.executable(any)).thenReturn('foo');
expect(await device.targetPlatform, TargetPlatform.darwin_x64);
expect(device.name, 'macOS');
expect(await device.installApp(mockMacOSApp), true);
@@ -54,7 +53,7 @@
tester 17193 0.0 0.2 4791128 37820 ?? S 2:27PM 0:00.09 /Applications/foo
''';
final MockMacOSApp mockMacOSApp = MockMacOSApp();
- when(mockMacOSApp.executable(any)).thenReturn('/Applications/foo');
+ when(mockMacOSApp.executable).thenReturn('tester');
when(mockProcessManager.run(<String>['ps', 'aux'])).thenAnswer((Invocation invocation) async {
return ProcessResult(1, 0, psOut, '');
});
@@ -68,27 +67,35 @@
});
group('startApp', () {
- final MockMacOSApp macOSApp = MockMacOSApp();
- final MockFileSystem mockFileSystem = MockFileSystem();
- final MockProcessManager mockProcessManager = MockProcessManager();
- final MockFile mockFile = MockFile();
- when(macOSApp.executable(any)).thenReturn('test');
- when(mockFileSystem.file('test')).thenReturn(mockFile);
- when(mockFile.existsSync()).thenReturn(true);
- when(mockProcessManager.start(<String>['test'])).thenAnswer((Invocation invocation) async {
- return FakeProcess(
- exitCode: Completer<int>().future,
- stdout: Stream<List<int>>.fromIterable(<List<int>>[
- utf8.encode('Observatory listening on http://127.0.0.1/0\n'),
- ]),
- stderr: const Stream<List<int>>.empty(),
- );
- });
- when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
- return ProcessResult(0, 1, '', '');
+ MockMacOSApp macOSApp;
+ MockFileSystem mockFileSystem;
+ MockProcessManager mockProcessManager;
+ MockFile mockFile;
+
+
+ setUp(() {
+ macOSApp = MockMacOSApp();
+ mockFileSystem = MockFileSystem();
+ mockProcessManager = MockProcessManager();
+ mockFile = MockFile();
+ when(mockFileSystem.file('test')).thenReturn(mockFile);
+ when(mockFile.existsSync()).thenReturn(true);
+ when(macOSApp.executable).thenReturn('test');
+ when(mockProcessManager.start(<String>['test'])).thenAnswer((Invocation invocation) async {
+ return FakeProcess(
+ exitCode: Completer<int>().future,
+ stdout: Stream<List<int>>.fromIterable(<List<int>>[
+ utf8.encode('Observatory listening on http://127.0.0.1/0\n'),
+ ]),
+ stderr: const Stream<List<int>>.empty(),
+ );
+ });
+ when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
+ return ProcessResult(0, 1, '', '');
+ });
});
- testUsingContext('Can run from prebuilt application', () async {
+ testUsingContext('can run from prebuilt application', () async {
final LaunchResult result = await device.startApp(macOSApp, prebuiltApplication: true);
expect(result.started, true);
expect(result.observatoryUri, Uri.parse('http://127.0.0.1/0'));
@@ -137,7 +144,7 @@
class MockPlatform extends Mock implements Platform {}
-class MockMacOSApp extends Mock implements MacOSApp {}
+class MockMacOSApp extends Mock implements PrebuiltMacOSApp {}
class MockFileSystem extends Mock implements FileSystem {}
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 644f828..9a1a7f7 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -68,6 +68,7 @@
fullRestart: anyNamed('fullRestart'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
+ dillOutputPath: anyNamed('dillOutputPath'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(
success: true,
@@ -175,6 +176,7 @@
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
+ dillOutputPath: anyNamed('dillOutputPath'),
)).thenThrow(RpcException(666, 'something bad happened'));
final OperationResult result = await residentRunner.restart(fullRestart: false);
@@ -279,6 +281,7 @@
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
+ dillOutputPath: anyNamed('dillOutputPath'),
)).thenThrow(RpcException(666, 'something bad happened'));
final OperationResult result = await residentRunner.restart(fullRestart: true);