blob: 8f39b9a55a95c4a4cd28755c93f1e96aefabc7aa [file] [log] [blame]
// 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/build.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.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/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';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../../src/common.dart';
import '../../../src/mocks.dart';
import '../../../src/testbed.dart';
void main() {
const BuildSystem buildSystem = BuildSystem();
Testbed testbed;
Environment androidEnvironment;
Environment iosEnvironment;
MockProcessManager mockProcessManager;
MockXcode mockXcode;
setUpAll(() {
Cache.disableLocking();
});
setUp(() {
mockXcode = MockXcode();
mockProcessManager = MockProcessManager();
testbed = Testbed(setup: () {
androidEnvironment = Environment(
outputDir: fs.currentDirectory,
projectDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: getNameForBuildMode(BuildMode.profile),
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
},
);
iosEnvironment = Environment(
outputDir: fs.currentDirectory,
projectDir: fs.currentDirectory,
defines: <String, String>{
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<String> paths = <String>[
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: <Type, Generator>{
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<MissingDefineException>());
}));
test('kernel_snapshot does not use track widget creation on profile builds', () => testbed.run(() async {
final MockKernelCompiler mockKernelCompiler = MockKernelCompiler();
when(kernelCompilerFactory.create(any)).thenAnswer((Invocation _) async {
return mockKernelCompiler;
});
when(mockKernelCompiler.compile(
sdkRoot: anyNamed('sdkRoot'),
aot: anyNamed('aot'),
trackWidgetCreation: false,
targetModel: anyNamed('targetModel'),
targetProductVm: anyNamed('targetProductVm'),
outputFilePath: anyNamed('outputFilePath'),
depFilePath: anyNamed('depFilePath'),
packagesPath: anyNamed('packagesPath'),
mainPath: anyNamed('mainPath'),
)).thenAnswer((Invocation _) async {
return const CompilerOutput('example', 0, <Uri>[]);
});
await const KernelSnapshot().build(androidEnvironment);
}, overrides: <Type, Generator>{
KernelCompilerFactory: () => MockKernelCompilerFactory(),
}));
test('kernel_snapshot does use track widget creation on debug builds', () => testbed.run(() async {
final MockKernelCompiler mockKernelCompiler = MockKernelCompiler();
when(kernelCompilerFactory.create(any)).thenAnswer((Invocation _) async {
return mockKernelCompiler;
});
when(mockKernelCompiler.compile(
sdkRoot: anyNamed('sdkRoot'),
aot: anyNamed('aot'),
trackWidgetCreation: true,
targetModel: anyNamed('targetModel'),
targetProductVm: anyNamed('targetProductVm'),
outputFilePath: anyNamed('outputFilePath'),
depFilePath: anyNamed('depFilePath'),
packagesPath: anyNamed('packagesPath'),
mainPath: anyNamed('mainPath'),
)).thenAnswer((Invocation _) async {
return const CompilerOutput('example', 0, <Uri>[]);
});
await const KernelSnapshot().build(Environment(
outputDir: fs.currentDirectory,
projectDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: 'debug',
kTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
}));
}, overrides: <Type, Generator>{
KernelCompilerFactory: () => MockKernelCompilerFactory(),
}));
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<MissingDefineException>());
}));
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<MissingDefineException>());
}));
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<MissingDefineException>());
}));
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<MissingDefineException>());
}));
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<Exception>());
}));
test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async {
iosEnvironment.defines[kIosArchs] ='armv7,arm64';
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
.createSync(recursive: true);
return FakeProcessResult(
stdout: '',
stderr: '',
);
});
final BuildResult result = await buildSystem
.build(const AotAssemblyProfile(), iosEnvironment);
expect(result.success, true);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
}));
test('aot_assembly_profile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async {
iosEnvironment.defines[kIosArchs] = 'arm64';
iosEnvironment.defines[kBitcodeFlag] = 'true';
final FakeProcessResult fakeProcessResult = FakeProcessResult(
stdout: '',
stderr: '',
);
final RunResult fakeRunResult = RunResult(fakeProcessResult, const <String>['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<RunResult>.value(fakeRunResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
expect(result.success, true);
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1);
verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(1);
}, overrides: <Type, Generator>{
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 <String>['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<RunResult>.value(fakeRunResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
expect(result.success, true);
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2);
verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2);
}, overrides: <Type, Generator>{
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(const AotAssemblyProfile(), iosEnvironment);
expect(result.success, true);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
}));
test('list dart sources handles packages without lib directories', () => testbed.run(() {
fs.file('.packages')
..createSync()
..writeAsStringSync('''
# Generated
example:fiz/lib/''');
fs.directory('fiz').createSync();
expect(listDartSources(androidEnvironment), <File>[]);
}));
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcode extends Mock implements Xcode {}
class FakeGenSnapshot implements GenSnapshot {
List<String> lastCallAdditionalArgs;
@override
Future<int> run({SnapshotType snapshotType, DarwinArch darwinArch, Iterable<String> additionalArgs = const <String>[]}) async {
lastCallAdditionalArgs = additionalArgs.toList();
final Directory out = fs.file(lastCallAdditionalArgs.last).parent;
if (darwinArch == null) {
out.childFile('app.so').createSync();
out.childFile('gen_snapshot.d').createSync();
return 0;
}
out.childDirectory('App.framework').childFile('App').createSync(recursive: true);
final String assembly = lastCallAdditionalArgs
.firstWhere((String arg) => arg.startsWith('--assembly'))
.substring('--assembly='.length);
fs.file(assembly).createSync();
fs.file(assembly.replaceAll('.S', '.o')).createSync();
return 0;
}
}
class FakeKernelCompilerFactory implements KernelCompilerFactory {
FakeKernelCompiler kernelCompiler = FakeKernelCompiler();
@override
Future<KernelCompiler> create(FlutterProject flutterProject) async {
return kernelCompiler;
}
}
class FakeKernelCompiler implements KernelCompiler {
@override
Future<CompilerOutput> compile({
String sdkRoot,
String mainPath,
String outputFilePath,
String depFilePath,
TargetModel targetModel = TargetModel.flutter,
bool linkPlatformKernelIn = false,
bool aot = false,
bool trackWidgetCreation,
List<String> extraFrontEndOptions,
String incrementalCompilerByteStorePath,
String packagesPath,
List<String> fileSystemRoots,
String fileSystemScheme,
bool targetProductVm = false,
String platformDill,
String initializeFromDill,
}) async {
fs.file(outputFilePath).createSync(recursive: true);
return CompilerOutput(outputFilePath, 0, null);
}
}
class MockKernelCompilerFactory extends Mock implements KernelCompilerFactory {}
class MockKernelCompiler extends Mock implements KernelCompiler {}