mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add experimentalBuildEnabled flag and initial shim for build_runner (#26989)
This commit is contained in:
parent
2c05d08f0c
commit
1237ee8f63
@ -0,0 +1,64 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
153
packages/flutter_tools/lib/src/build_runner/build_runner.dart
Normal file
153
packages/flutter_tools/lib/src/build_runner/build_runner.dart
Normal file
@ -0,0 +1,153 @@
|
||||
// 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),
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
// 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 {}
|
||||
104
packages/flutter_tools/test/build_runner/build_runner_test.dart
Normal file
104
packages/flutter_tools/test/build_runner/build_runner_test.dart
Normal file
@ -0,0 +1,104 @@
|
||||
// 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 {}
|
||||
Loading…
x
Reference in New Issue
Block a user