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 bc31ad3bea..eb708aa09d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -51,7 +51,7 @@ class MacOSAssetBehavior extends SourceBehavior { ); final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir); final String prefix = fs.path.join(flutterProject.macos.ephemeralDirectory.path, - 'App.framework', 'flutter_assets'); + 'App.framework', 'Resources', 'flutter_assets'); final List results = []; for (String key in assetBundle.entries.keys) { final File file = fs.file(fs.path.join(prefix, key)); @@ -141,9 +141,8 @@ class DebugMacOSFramework extends Target { @override Future build(List inputFiles, Environment environment) async { - final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir); final File outputFile = fs.file(fs.path.join( - flutterProject.macos.ephemeralDirectory.path, 'App.framework', 'App')); + environment.buildDir.path, 'App.framework', 'App')); outputFile.createSync(recursive: true); final File debugApp = environment.buildDir.childFile('debug_app.cc') ..writeAsStringSync(r''' @@ -158,7 +157,7 @@ static const int Moo = 88; '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', '-install_name', '@rpath/App.framework/App', - '-o', 'macos/Flutter/ephemeral/App.framework/App', + '-o', outputFile.path, ]); if (result.exitCode != 0) { throw Exception('Failed to compile debug App.framework'); @@ -175,11 +174,14 @@ static const int Moo = 88; @override List get outputs => const [ - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/App'), + Source.pattern('{BUILD_DIR}/App.framework/App'), ]; } /// Bundle the flutter assets, app.dill, and precompiled runtimes into the App.framework. +/// +/// See https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html +/// for more information on Framework structure. class DebugBundleFlutterAssets extends Target { const DebugBundleFlutterAssets(); @@ -189,19 +191,30 @@ class DebugBundleFlutterAssets extends Target { @override Future build(List inputFiles, Environment environment) async { final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir); - final Directory outputDirectory = flutterProject.macos - .ephemeralDirectory.childDirectory('App.framework'); - if (!outputDirectory.existsSync()) { - throw Exception('App.framework must exist to bundle assets.'); - } + final Directory frameworkRootDirectory = flutterProject.macos + .ephemeralDirectory + .childDirectory('App.framework'); + final Directory outputDirectory = frameworkRootDirectory + .childDirectory('Versions') + .childDirectory('A') + ..createSync(recursive: true); + + // Copy App into framework directory. + environment.buildDir + .childDirectory('App.framework') + .childFile('App') + .copySync(outputDirectory.childFile('App').path); + // Copy assets into asset directory. - final Directory assetDirectory = outputDirectory.childDirectory('flutter_assets'); + final Directory assetDirectory = outputDirectory + .childDirectory('Resources') + .childDirectory('flutter_assets'); // We're not smart enough to only remove assets that are removed. If // anything changes blow away the whole directory. if (assetDirectory.existsSync()) { assetDirectory.deleteSync(recursive: true); } - assetDirectory.createSync(); + assetDirectory.createSync(recursive: true); final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); final int result = await assetBundle.build( manifestPath: environment.projectDir.childFile('pubspec.yaml').path, @@ -224,9 +237,38 @@ class DebugBundleFlutterAssets extends Target { resource.release(); } })); - } catch (err, st){ + } catch (err, st) { throw Exception('Failed to copy assets: $st'); } + // Copy Info.plist template. + assetDirectory.parent.childFile('Info.plist') + ..createSync() + ..writeAsStringSync(r''' + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + + + +'''); + // Copy dill file. try { final File sourceFile = environment.buildDir.childFile('app.dill'); @@ -247,6 +289,29 @@ class DebugBundleFlutterAssets extends Target { } catch (err) { throw Exception('Failed to copy precompiled runtimes: $err'); } + // Create symlink to current version. + try { + final Link currentVersion = outputDirectory.parent + .childLink('Current'); + if (!currentVersion.existsSync()) { + currentVersion.createSync(outputDirectory.path); + } + // Create symlink to current resources. + final Link currentResources = frameworkRootDirectory + .childLink('Resources'); + if (!currentResources.existsSync()) { + currentResources.createSync(fs.path.join(currentVersion.path, 'Resources')); + } + // Create symlink to current binary. + final Link currentFramework = frameworkRootDirectory + .childLink('App'); + if (!currentFramework.existsSync()) { + currentFramework.createSync(fs.path.join(currentVersion.path, 'App')); + } + } on FileSystemException { + throw Exception('Failed to create symlinks for framework. try removing ' + 'the "${flutterProject.macos.ephemeralDirectory.path}" directory and rerunning'); + } } @override @@ -268,11 +333,13 @@ class DebugBundleFlutterAssets extends Target { @override List get outputs => const [ Source.behavior(MacOSAssetBehavior()), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/AssetManifest.json'), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/FontManifest.json'), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/LICENSE'), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/kernel_blob.bin'), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/vm_snapshot_data'), - Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/flutter_assets/isolate_snapshot_data'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/App'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/Info.plist'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/AssetManifest.json'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/FontManifest.json'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/LICENSE'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'), + Source.pattern('{PROJECT_DIR}/macos/Flutter/ephemeral/App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'), ]; } 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 27a67b67ea..ac6ef94963 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 @@ -118,11 +118,15 @@ void main() { 'vm_isolate_snapshot.bin')).createSync(recursive: true); fs.file(fs.path.join('bin', 'cache', 'artifacts', 'engine', 'darwin-x64', 'isolate_snapshot.bin')).createSync(recursive: true); + fs.file(fs.path.join(environment.buildDir.path, 'App.framework', 'App')) + ..createSync(recursive: true); final String frameworkPath = fs.path.join(environment.projectDir.path, 'macos', 'Flutter', 'ephemeral', 'App.framework'); final String inputKernel = fs.path.join(environment.buildDir.path, 'app.dill'); fs.directory(frameworkPath).createSync(recursive: true); - final String outputKernel = fs.path.join(frameworkPath, 'flutter_assets', 'kernel_blob.bin'); + final String outputKernel = fs.path.join(frameworkPath, 'Resources', + 'flutter_assets', 'kernel_blob.bin'); + final String outputPlist = fs.path.join(frameworkPath, 'Resources', 'Info.plist'); fs.file(inputKernel) ..createSync(recursive: true) ..writeAsStringSync('testing'); @@ -130,6 +134,7 @@ void main() { await const DebugBundleFlutterAssets().build([], environment); expect(fs.file(outputKernel).readAsStringSync(), 'testing'); + expect(fs.file(outputPlist).readAsStringSync(), contains('io.flutter.flutter.app')); })); }