blob: 68be86184e94ce98bbb5c4e4fc605cd74e1c24b1 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/macos.dart';
import 'package:flutter_tools/src/convert.dart';
import '../../../src/common.dart';
import '../../../src/context.dart';
import '../../../src/fake_process_manager.dart';
void main() {
late Environment environment;
late FileSystem fileSystem;
late Artifacts artifacts;
late FakeProcessManager processManager;
late File binary;
late BufferLogger logger;
late FakeCommand copyFrameworkCommand;
late FakeCommand lipoInfoNonFatCommand;
late FakeCommand lipoInfoFatCommand;
late FakeCommand lipoVerifyX86_64Command;
setUp(() {
processManager = FakeProcessManager.empty();
artifacts = Artifacts.test();
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
environment = Environment.test(
fileSystem.currentDirectory,
defines: <String, String>{
kBuildMode: 'debug',
kTargetPlatform: 'darwin',
kDarwinArchs: 'x86_64',
},
inputs: <String, String>{},
artifacts: artifacts,
processManager: processManager,
logger: logger,
fileSystem: fileSystem,
engineVersion: '2'
);
binary = environment.outputDir
.childDirectory('FlutterMacOS.framework')
.childDirectory('Versions')
.childDirectory('A')
.childFile('FlutterMacOS');
copyFrameworkCommand = FakeCommand(
command: <String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'Artifact.flutterMacOSFramework.debug',
environment.outputDir.path,
],
);
lipoInfoNonFatCommand = FakeCommand(command: <String>[
'lipo',
'-info',
binary.path,
], stdout: 'Non-fat file:');
lipoInfoFatCommand = FakeCommand(command: <String>[
'lipo',
'-info',
binary.path,
], stdout: 'Architectures in the fat file:');
lipoVerifyX86_64Command = FakeCommand(command: <String>[
'lipo',
binary.path,
'-verify_arch',
'x86_64',
]);
});
testUsingContext('Copies files to correct cache directory', () async {
binary.createSync(recursive: true);
processManager.addCommands(<FakeCommand>[
copyFrameworkCommand,
lipoInfoNonFatCommand,
lipoVerifyX86_64Command,
]);
await const DebugUnpackMacOS().build(environment);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('thinning fails when framework missing', () async {
processManager.addCommand(copyFrameworkCommand);
await expectLater(
const DebugUnpackMacOS().build(environment),
throwsA(isException.having(
(Exception exception) => exception.toString(),
'description',
contains('FlutterMacOS.framework/Versions/A/FlutterMacOS does not exist, cannot thin'),
)),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('lipo fails when arch missing from framework', () async {
environment.defines[kDarwinArchs] = 'arm64 x86_64';
binary.createSync(recursive: true);
processManager.addCommands(<FakeCommand>[
copyFrameworkCommand,
lipoInfoFatCommand,
FakeCommand(command: <String>[
'lipo',
binary.path,
'-verify_arch',
'arm64',
'x86_64',
], exitCode: 1),
]);
await expectLater(
const DebugUnpackMacOS().build(environment),
throwsA(isException.having(
(Exception exception) => exception.toString(),
'description',
contains('does not contain arm64 x86_64. Running lipo -info:\nArchitectures in the fat file:'),
)),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('skips thins framework', () async {
binary.createSync(recursive: true);
processManager.addCommands(<FakeCommand>[
copyFrameworkCommand,
lipoInfoNonFatCommand,
lipoVerifyX86_64Command,
]);
await const DebugUnpackMacOS().build(environment);
expect(logger.traceText, contains('Skipping lipo for non-fat file /FlutterMacOS.framework/Versions/A/FlutterMacOS'));
});
testUsingContext('thins fat framework', () async {
binary.createSync(recursive: true);
processManager.addCommands(<FakeCommand>[
copyFrameworkCommand,
lipoInfoFatCommand,
lipoVerifyX86_64Command,
FakeCommand(command: <String>[
'lipo',
'-output',
binary.path,
'-extract',
'x86_64',
binary.path,
]),
]);
await const DebugUnpackMacOS().build(environment);
expect(processManager, hasNoRemainingExpectations);
});
testUsingContext('debug macOS application fails if App.framework missing', () async {
fileSystem.directory(
artifacts.getArtifactPath(
Artifact.flutterMacOSFramework,
mode: BuildMode.debug,
))
.createSync();
final String inputKernel = fileSystem.path.join(environment.buildDir.path, 'app.dill');
fileSystem.file(inputKernel)
..createSync(recursive: true)
..writeAsStringSync('testing');
expect(() async => const DebugMacOSBundleFlutterAssets().build(environment),
throwsException);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debug macOS application creates correctly structured framework', () async {
fileSystem.directory(
artifacts.getArtifactPath(
Artifact.flutterMacOSFramework,
mode: BuildMode.debug,
))
.createSync();
environment.defines[kBundleSkSLPath] = 'bundle.sksl';
fileSystem.file(
artifacts.getArtifactPath(
Artifact.vmSnapshotData,
platform: TargetPlatform.darwin,
mode: BuildMode.debug,
)).createSync(recursive: true);
fileSystem.file(
artifacts.getArtifactPath(
Artifact.isolateSnapshotData,
platform: TargetPlatform.darwin,
mode: BuildMode.debug,
)).createSync(recursive: true);
fileSystem.file('${environment.buildDir.path}/App.framework/App')
.createSync(recursive: true);
// sksl bundle
fileSystem.file('bundle.sksl').writeAsStringSync(json.encode(
<String, Object>{
'engineRevision': '2',
'platform': 'ios',
'data': <String, Object>{
'A': 'B',
},
},
));
final String inputKernel = '${environment.buildDir.path}/app.dill';
fileSystem.file(inputKernel)
..createSync(recursive: true)
..writeAsStringSync('testing');
await const DebugMacOSBundleFlutterAssets().build(environment);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin').readAsStringSync(),
'testing',
);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/Info.plist').readAsStringSync(),
contains('io.flutter.flutter.app'),
);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
exists,
);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
exists,
);
final File skslFile = fileSystem.file('App.framework/Versions/A/Resources/flutter_assets/io.flutter.shaders.json');
expect(skslFile, exists);
expect(skslFile.readAsStringSync(), '{"data":{"A":"B"}}');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('release/profile macOS application has no blob or precompiled runtime', () async {
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
.createSync(recursive: true);
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
.createSync(recursive: true);
fileSystem.file('${environment.buildDir.path}/App.framework/App')
.createSync(recursive: true);
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'),
isNot(exists),
);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
isNot(exists),
);
expect(fileSystem.file(
'App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
isNot(exists),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('release/profile macOS application updates when App.framework updates', () async {
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin')
.createSync(recursive: true);
fileSystem.file('bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin')
.createSync(recursive: true);
final File inputFramework = fileSystem.file(fileSystem.path.join(environment.buildDir.path, 'App.framework', 'App'))
..createSync(recursive: true)
..writeAsStringSync('ABC');
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
final File outputFramework = fileSystem.file(fileSystem.path.join(environment.outputDir.path, 'App.framework', 'App'));
expect(outputFramework.readAsStringSync(), 'ABC');
inputFramework.writeAsStringSync('DEF');
await const ProfileMacOSBundleFlutterAssets().build(environment..defines[kBuildMode] = 'profile');
expect(outputFramework.readAsStringSync(), 'DEF');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('DebugMacOSFramework creates expected binary with arm64 only arch', () async {
environment.defines[kDarwinArchs] = 'arm64';
processManager.addCommand(
FakeCommand(command: <String>[
'xcrun',
'clang',
'-x',
'c',
environment.buildDir.childFile('debug_app.cc').path,
'-arch',
'arm64',
'-dynamiclib',
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o',
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.path,
]),
);
await const DebugMacOSFramework().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('DebugMacOSFramework creates universal binary', () async {
environment.defines[kDarwinArchs] = 'arm64 x86_64';
processManager.addCommand(
FakeCommand(command: <String>[
'xcrun',
'clang',
'-x',
'c',
environment.buildDir.childFile('debug_app.cc').path,
'-arch',
'arm64',
'-arch',
'x86_64',
'-dynamiclib',
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o',
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.path,
]),
);
await const DebugMacOSFramework().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('CompileMacOSFramework creates universal binary', () async {
environment.defines[kDarwinArchs] = 'arm64 x86_64';
environment.defines[kBuildMode] = 'release';
processManager.addCommands(<FakeCommand>[
FakeCommand(command: <String>[
'Artifact.genSnapshot.TargetPlatform.darwin.release_arm64',
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=${environment.buildDir.childFile('arm64/snapshot_assembly.S').path}',
'--strip',
environment.buildDir.childFile('app.dill').path,
]),
FakeCommand(command: <String>[
'Artifact.genSnapshot.TargetPlatform.darwin.release_x64',
'--deterministic',
'--snapshot_kind=app-aot-assembly',
'--assembly=${environment.buildDir.childFile('x86_64/snapshot_assembly.S').path}',
'--strip',
environment.buildDir.childFile('app.dill').path,
]),
FakeCommand(command: <String>[
'xcrun', 'cc', '-arch', 'arm64',
'-c', environment.buildDir.childFile('arm64/snapshot_assembly.S').path,
'-o', environment.buildDir.childFile('arm64/snapshot_assembly.o').path,
]),
FakeCommand(command: <String>[
'xcrun', 'cc', '-arch', 'x86_64',
'-c', environment.buildDir.childFile('x86_64/snapshot_assembly.S').path,
'-o', environment.buildDir.childFile('x86_64/snapshot_assembly.o').path,
]),
FakeCommand(command: <String>[
'xcrun', 'clang', '-arch', 'arm64', '-dynamiclib', '-Xlinker', '-rpath',
'-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath',
'-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o', environment.buildDir.childFile('arm64/App.framework/App').path,
environment.buildDir.childFile('arm64/snapshot_assembly.o').path,
]),
FakeCommand(command: <String>[
'xcrun', 'clang', '-arch', 'x86_64', '-dynamiclib', '-Xlinker', '-rpath',
'-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath',
'-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o', environment.buildDir.childFile('x86_64/App.framework/App').path,
environment.buildDir.childFile('x86_64/snapshot_assembly.o').path,
]),
FakeCommand(command: <String>[
'lipo',
environment.buildDir.childFile('arm64/App.framework/App').path,
environment.buildDir.childFile('x86_64/App.framework/App').path,
'-create',
'-output',
environment.buildDir.childFile('App.framework/App').path,
]),
]);
await const CompileMacOSFramework().build(environment);
expect(processManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
}