Unverified Commit 1237ee8f authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add experimentalBuildEnabled flag and initial shim for build_runner (#26989)

parent 2c05d08f
// 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 '../base/file_system.dart';
import '../compile.dart';
import '../globals.dart';
import 'build_runner.dart';
/// An implementation of the [KernelCompiler] which delegates to build_runner.
///
/// Only a subset of the arguments provided to the [KernelCompiler] are
/// supported here. Using the build pipeline implies a fixed multiroot
/// filesystem and requires a pubspec.
///
/// This is only safe to use if [experimentalBuildEnabled] is true.
class BuildKernelCompiler implements KernelCompiler {
const BuildKernelCompiler();
@override
Future<CompilerOutput> compile({
String mainPath,
String outputFilePath,
bool linkPlatformKernelIn = false,
bool aot = false,
bool trackWidgetCreation,
List<String> extraFrontEndOptions,
String incrementalCompilerByteStorePath,
bool targetProductVm = false,
// These arguments are currently unused.
String sdkRoot,
String packagesPath,
List<String> fileSystemRoots,
String fileSystemScheme,
String depFilePath,
TargetModel targetModel,
}) async {
if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) {
printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,'
'sdkRoot, packagesPath are not supported when using the experimental '
'build* pipeline');
}
final BuildRunner buildRunner = buildRunnerFactory.create();
try {
final BuildResult buildResult = await buildRunner.build(
aot: aot,
linkPlatformKernelIn: linkPlatformKernelIn,
trackWidgetCreation: trackWidgetCreation,
mainPath: mainPath,
targetProductVm: targetProductVm,
extraFrontEndOptions: extraFrontEndOptions
);
final File outputFile = fs.file(outputFilePath);
if (!await outputFile.exists()) {
await outputFile.create();
}
await outputFile.writeAsBytes(await buildResult.dillFile.readAsBytes());
return CompilerOutput(outputFilePath, 0);
} on Exception catch (err) {
printError('Compilation Failed: $err');
return const CompilerOutput(null, 1);
}
}
}
// 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 'dart:async';
import 'dart:convert';
import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../cache.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../project.dart';
/// The [BuildRunnerFactory] instance.
BuildRunnerFactory get buildRunnerFactory => context[BuildRunnerFactory];
/// Whether to attempt to build a flutter project using build* libraries.
///
/// This requires both an experimental opt in via the environment variable
/// 'FLUTTER_EXPERIMENTAL_BUILD' and that the project itself has a
/// dependency on the package 'flutter_build' and 'build_runner.'
FutureOr<bool> get experimentalBuildEnabled async {
if (_experimentalBuildEnabled != null) {
return _experimentalBuildEnabled;
}
final bool flagEnabled = platform.environment['FLUTTER_EXPERIMENTAL_BUILD']?.toLowerCase() == 'true';
if (!flagEnabled) {
return _experimentalBuildEnabled = false;
}
final FlutterProject flutterProject = await FlutterProject.current();
final Map<String, Uri> packages = PackageMap(flutterProject.packagesFile.path).map;
return _experimentalBuildEnabled = packages.containsKey('flutter_build') && packages.containsKey('build_runner');
}
bool _experimentalBuildEnabled;
@visibleForTesting
set experimentalBuildEnabled(bool value) {
_experimentalBuildEnabled = value;
}
/// An injectable factory to create instances of [BuildRunner].
class BuildRunnerFactory {
const BuildRunnerFactory();
/// Creates a new [BuildRunner] instance.
BuildRunner create() {
return BuildRunner();
}
}
/// A wrapper for a build_runner process which delegates to a generated
/// build script.
///
/// This is only enabled if [experimentalBuildEnabled] is true, and only for
/// external flutter users.
class BuildRunner {
/// Run a build_runner build and return the resulting .packages and dill file.
///
/// The defines of the build command are the arguments required in the
/// flutter_build kernel builder.
Future<BuildResult> build({
@required bool aot,
@required bool linkPlatformKernelIn,
@required bool trackWidgetCreation,
@required bool targetProductVm,
@required String mainPath,
@required List<String> extraFrontEndOptions,
}) async {
final FlutterProject flutterProject = await FlutterProject.current();
final String frontendServerPath = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
);
final String pubExecutable = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk','bin', 'pub');
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
final String packagesPath = flutterProject.packagesFile.absolute.path;
final Process process = await processManager.start(<String>[
'$pubExecutable',
'run',
'build_runner',
'build',
'--define', 'flutter_build|kernel=disabled=false',
'--define', 'flutter_build|kernel=aot=$aot',
'--define', 'flutter_build|kernel=linkPlatformKernelIn=$linkPlatformKernelIn',
'--define', 'flutter_build|kernel=trackWidgetCreation=$trackWidgetCreation',
'--define', 'flutter_build|kernel=targetProductVm=$targetProductVm',
'--define', 'flutter_build|kernel=mainPath=$mainPath',
'--define', 'flutter_build|kernel=packagesPath=$packagesPath',
'--define', 'flutter_build|kernel=sdkRoot=$sdkRoot',
'--define', 'flutter_build|kernel=frontendServerPath=$frontendServerPath',
'--define', 'flutter_build|kernel=engineDartBinaryPath=$engineDartBinaryPath',
'--define', 'flutter_build|kernel=extraFrontEndOptions=${extraFrontEndOptions ?? const <String>[]}',
]);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_handleOutput);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_handleError);
final int exitCode = await process.exitCode;
if (exitCode != 0) {
throw Exception('build_runner exited with non-zero exit code: $exitCode');
}
/// We don't check for this above because it might be generated for the
/// first time by invoking the build.
final Directory dartTool = flutterProject.dartTool;
final String projectName = flutterProject.manifest.appName;
final Directory generatedDirectory = dartTool
.absolute
.childDirectory('build')
.childDirectory('generated')
.childDirectory(projectName);
if (!await generatedDirectory.exists()) {
throw Exception('build_runner cannot find generated directory');
}
final String relativeMain = fs.path.relative(mainPath, from: flutterProject.directory.path);
final File packagesFile = fs.file(
fs.path.join(generatedDirectory.path, fs.path.setExtension(relativeMain, '.packages'))
);
final File dillFile = fs.file(
fs.path.join(generatedDirectory.path, fs.path.setExtension(relativeMain, '.app.dill'))
);
if (!await packagesFile.exists() || !await dillFile.exists()) {
throw Exception('build_runner did not produce output at expected location: ${dillFile.path} missing');
}
return BuildResult(packagesFile, dillFile);
}
void _handleOutput(String line) {
printTrace(line);
}
void _handleError(String line) {
printError(line);
}
}
class BuildResult {
const BuildResult(this.packagesFile, this.dillFile);
final File packagesFile;
final File dillFile;
}
......@@ -21,6 +21,7 @@ import 'base/platform.dart';
import 'base/time.dart';
import 'base/user_messages.dart';
import 'base/utils.dart';
import 'build_runner/build_runner.dart';
import 'cache.dart';
import 'compile.dart';
import 'devfs.dart';
......@@ -59,6 +60,7 @@ Future<T> runInContext<T>(
Artifacts: () => CachedArtifacts(),
AssetBundleFactory: () => AssetBundleFactory.defaultInstance,
BotDetector: () => const BotDetector(),
BuildRunnerFactory: () => const BuildRunnerFactory(),
Cache: () => Cache(),
CocoaPods: () => CocoaPods(),
CocoaPodsValidator: () => const CocoaPodsValidator(),
......
......@@ -103,6 +103,9 @@ class FlutterProject {
/// The `.flutter-plugins` file of this project.
File get flutterPluginsFile => directory.childFile('.flutter-plugins');
/// The `.dart-tool` directory of this project.
Directory get dartTool => directory.childDirectory('.dart_tool');
/// The example sub-project of this project.
FlutterProject get example => FlutterProject(
_exampleDirectory(directory),
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_runner/build_kernel_compiler.dart';
import 'package:flutter_tools/src/build_runner/build_runner.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:mockito/mockito.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
group(BuildKernelCompiler, () {
final MockBuildRunnerFactory mockBuildRunnerFactory = MockBuildRunnerFactory();
final MockBuildRunner mockBuildRunner = MockBuildRunner();
final MockFileSystem mockFileSystem = MockFileSystem();
final MockFile packagesFile = MockFile();
final MockFile dillFile = MockFile();
final MockFile outputFile = MockFile();
when(mockFileSystem.file('main.app.dill')).thenReturn(dillFile);
when(mockFileSystem.file('.packages')).thenReturn(packagesFile);
when(mockFileSystem.file('output.app.dill')).thenReturn(outputFile);
when(packagesFile.exists()).thenAnswer((Invocation invocation) async => true);
when(dillFile.exists()).thenAnswer((Invocation invocation) async => true);
when(outputFile.exists()).thenAnswer((Invocation invocation) async => true);
when(mockBuildRunnerFactory.create()).thenReturn(mockBuildRunner);
when(dillFile.readAsBytes()).thenAnswer((Invocation invocation) async => <int>[0, 1, 2, 3]);
testUsingContext('delegates to build_runner', () async {
const BuildKernelCompiler kernelCompiler = BuildKernelCompiler();
when(mockBuildRunner.build(
aot: anyNamed('aot'),
extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'),
mainPath: anyNamed('mainPath'),
targetProductVm: anyNamed('targetProductVm'),
trackWidgetCreation: anyNamed('trackWidgetCreation')
)).thenAnswer((Invocation invocation) async {
return BuildResult(fs.file('.packages'), fs.file('main.app.dill'));
});
final CompilerOutput buildResult = await kernelCompiler.compile(
outputFilePath: 'output.app.dill',
);
expect(buildResult.outputFilename, 'output.app.dill');
expect(buildResult.errorCount, 0);
verify(outputFile.writeAsBytes(<int>[0, 1, 2, 3])).called(1);
}, overrides: <Type, Generator>{
BuildRunnerFactory: () => mockBuildRunnerFactory,
FileSystem: () => mockFileSystem,
});
});
}
class MockBuildRunnerFactory extends Mock implements BuildRunnerFactory {}
class MockBuildRunner extends Mock implements BuildRunner {}
class MockFileSystem extends Mock implements FileSystem {}
class MockFile extends Mock implements File {}
// 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 'dart:convert';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_runner/build_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('experimentalBuildEnabled', () {
final MockProcessManager mockProcessManager = MockProcessManager();
final MockPlatform mockPlatform = MockPlatform();
final MockFileSystem mockFileSystem = MockFileSystem();
setUp(() {
experimentalBuildEnabled = null;
});
testUsingContext('is enabled if environment variable is enabled and project '
'contains a dependency on flutter_build and build_runner', () async {
final MockDirectory projectDirectory = MockDirectory();
final MockDirectory exampleDirectory = MockDirectory();
final MockFile packagesFile = MockFile();
final MockFile pubspecFile = MockFile();
final MockFile examplePubspecFile = MockFile();
const String packages = r'''
flutter_build:file:///Users/tester/.pub-cache/hosted/pub.dartlang.org/flutter_build/lib/
build_runner:file:///Users/tester/.pub-cache/hosted/pub.dartlang.org/build_runner/lib/
example:lib/
''';
when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_EXPERIMENTAL_BUILD': 'true'});
when(mockFileSystem.currentDirectory).thenReturn(projectDirectory);
when(mockFileSystem.isFileSync(any)).thenReturn(false);
when(projectDirectory.childFile('pubspec.yaml')).thenReturn(pubspecFile);
when(projectDirectory.childFile('.packages')).thenReturn(packagesFile);
when(projectDirectory.childDirectory('example')).thenReturn(exampleDirectory);
when(exampleDirectory.childFile('pubspec.yaml')).thenReturn(examplePubspecFile);
when(packagesFile.path).thenReturn('/test/.packages');
when(pubspecFile.path).thenReturn('/test/pubspec.yaml');
when(examplePubspecFile.path).thenReturn('/test/example/pubspec.yaml');
when(mockFileSystem.file('/test/.packages')).thenReturn(packagesFile);
when(packagesFile.readAsBytesSync()).thenReturn(utf8.encode(packages));
expect(await experimentalBuildEnabled, true);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => mockPlatform,
FileSystem: () => mockFileSystem,
});
testUsingContext('is not enabled if environment variable is enabled and project '
'does not contain a dependency on flutter_build', () async {
final MockDirectory projectDirectory = MockDirectory();
final MockDirectory exampleDirectory = MockDirectory();
final MockFile packagesFile = MockFile();
final MockFile pubspecFile = MockFile();
final MockFile examplePubspecFile = MockFile();
const String packages = r'''
build_runner:file:///Users/tester/.pub-cache/hosted/pub.dartlang.org/build_runner/lib/
example:lib/
''';
when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_EXPERIMENTAL_BUILD': 'true'});
when(mockFileSystem.currentDirectory).thenReturn(projectDirectory);
when(mockFileSystem.isFileSync(any)).thenReturn(false);
when(projectDirectory.childFile('pubspec.yaml')).thenReturn(pubspecFile);
when(projectDirectory.childFile('.packages')).thenReturn(packagesFile);
when(projectDirectory.childDirectory('example')).thenReturn(exampleDirectory);
when(exampleDirectory.childFile('pubspec.yaml')).thenReturn(examplePubspecFile);
when(packagesFile.path).thenReturn('/test/.packages');
when(pubspecFile.path).thenReturn('/test/pubspec.yaml');
when(examplePubspecFile.path).thenReturn('/test/example/pubspec.yaml');
when(mockFileSystem.file('/test/.packages')).thenReturn(packagesFile);
when(packagesFile.readAsBytesSync()).thenReturn(utf8.encode(packages));
expect(await experimentalBuildEnabled, false);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => mockPlatform,
FileSystem: () => mockFileSystem,
});
testUsingContext('is not enabed if environment varable is not enabled', () async {
when(mockPlatform.environment).thenReturn(<String, String>{});
expect(await experimentalBuildEnabled, false);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => mockPlatform,
FileSystem: () => mockFileSystem,
});
});
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockPlatform extends Mock implements Platform {}
class MockFileSystem extends Mock implements FileSystem {}
class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment