Unverified Commit fa35698e authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] allow passing non-config inputs (#54228)

parent 589b14d8
...@@ -295,6 +295,7 @@ class Environment { ...@@ -295,6 +295,7 @@ class Environment {
@required ProcessManager processManager, @required ProcessManager processManager,
Directory buildDir, Directory buildDir,
Map<String, String> defines = const <String, String>{}, Map<String, String> defines = const <String, String>{},
Map<String, String> inputs = const <String, String>{},
}) { }) {
// Compute a unique hash of this build's particular environment. // Compute a unique hash of this build's particular environment.
// Sort the keys by key so that the result is stable. We always // Sort the keys by key so that the result is stable. We always
...@@ -325,6 +326,7 @@ class Environment { ...@@ -325,6 +326,7 @@ class Environment {
logger: logger, logger: logger,
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
inputs: inputs,
); );
} }
...@@ -338,6 +340,7 @@ class Environment { ...@@ -338,6 +340,7 @@ class Environment {
Directory flutterRootDir, Directory flutterRootDir,
Directory buildDir, Directory buildDir,
Map<String, String> defines = const <String, String>{}, Map<String, String> defines = const <String, String>{},
Map<String, String> inputs = const <String, String>{},
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required Logger logger, @required Logger logger,
@required Artifacts artifacts, @required Artifacts artifacts,
...@@ -350,6 +353,7 @@ class Environment { ...@@ -350,6 +353,7 @@ class Environment {
flutterRootDir: flutterRootDir ?? testDirectory, flutterRootDir: flutterRootDir ?? testDirectory,
buildDir: buildDir, buildDir: buildDir,
defines: defines, defines: defines,
inputs: inputs,
fileSystem: fileSystem, fileSystem: fileSystem,
logger: logger, logger: logger,
artifacts: artifacts, artifacts: artifacts,
...@@ -369,6 +373,7 @@ class Environment { ...@@ -369,6 +373,7 @@ class Environment {
@required this.logger, @required this.logger,
@required this.fileSystem, @required this.fileSystem,
@required this.artifacts, @required this.artifacts,
@required this.inputs,
}); });
/// The [Source] value which is substituted with the path to [projectDir]. /// The [Source] value which is substituted with the path to [projectDir].
...@@ -420,6 +425,16 @@ class Environment { ...@@ -420,6 +425,16 @@ class Environment {
/// which prevents the config from leaking into different builds. /// which prevents the config from leaking into different builds.
final Map<String, String> defines; final Map<String, String> defines;
/// Additional input files passed to the build targets.
///
/// Unlike [defines], values set here do not force a new build configuration.
/// This is useful for passing file inputs that may have changing paths
/// without running builds from scratch.
///
/// It is the responsibility of the [Target] to declare that an input was
/// used in an output depfile.
final Map<String, String> inputs;
/// The root build directory shared by all builds. /// The root build directory shared by all builds.
final Directory rootBuildDir; final Directory rootBuildDir;
......
...@@ -64,6 +64,13 @@ class AssembleCommand extends FlutterCommand { ...@@ -64,6 +64,13 @@ class AssembleCommand extends FlutterCommand {
abbr: 'd', abbr: 'd',
help: 'Allows passing configuration to a target with --define=target=key=value.', help: 'Allows passing configuration to a target with --define=target=key=value.',
); );
argParser.addMultiOption(
'input',
abbr: 'i',
help: 'Allows passing additional inputs with --input=key=value. Unlike '
'defines, additional inputs do not generate a new configuration, instead '
'they are treated as dependencies of the targets that use them.'
);
argParser.addOption('depfile', help: 'A file path where a depfile will be written. ' argParser.addOption('depfile', help: 'A file path where a depfile will be written. '
'This contains all build inputs and outputs in a make style syntax' 'This contains all build inputs and outputs in a make style syntax'
); );
...@@ -99,7 +106,7 @@ class AssembleCommand extends FlutterCommand { ...@@ -99,7 +106,7 @@ class AssembleCommand extends FlutterCommand {
return const <CustomDimensions, String>{}; return const <CustomDimensions, String>{};
} }
try { try {
final Environment localEnvironment = environment; final Environment localEnvironment = createEnvironment();
return <CustomDimensions, String>{ return <CustomDimensions, String>{
CustomDimensions.commandBuildBundleTargetPlatform: localEnvironment.defines['TargetPlatform'], CustomDimensions.commandBuildBundleTargetPlatform: localEnvironment.defines['TargetPlatform'],
CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}', CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}',
...@@ -111,7 +118,7 @@ class AssembleCommand extends FlutterCommand { ...@@ -111,7 +118,7 @@ class AssembleCommand extends FlutterCommand {
} }
/// The target(s) we are building. /// The target(s) we are building.
List<Target> get targets { List<Target> createTargets() {
if (argResults.rest.isEmpty) { if (argResults.rest.isEmpty) {
throwToolExit('missing target name for flutter assemble.'); throwToolExit('missing target name for flutter assemble.');
} }
...@@ -132,7 +139,7 @@ class AssembleCommand extends FlutterCommand { ...@@ -132,7 +139,7 @@ class AssembleCommand extends FlutterCommand {
} }
/// The environmental configuration for a build invocation. /// The environmental configuration for a build invocation.
Environment get environment { Environment createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
String output = stringArg('output'); String output = stringArg('output');
if (output == null) { if (output == null) {
...@@ -149,6 +156,7 @@ class AssembleCommand extends FlutterCommand { ...@@ -149,6 +156,7 @@ class AssembleCommand extends FlutterCommand {
.childDirectory('flutter_build'), .childDirectory('flutter_build'),
projectDir: flutterProject.directory, projectDir: flutterProject.directory,
defines: _parseDefines(stringsArg('define')), defines: _parseDefines(stringsArg('define')),
inputs: _parseDefines(stringsArg('input')),
cacheDir: globals.cache.getRoot(), cacheDir: globals.cache.getRoot(),
flutterRootDir: globals.fs.directory(Cache.flutterRoot), flutterRootDir: globals.fs.directory(Cache.flutterRoot),
artifacts: globals.artifacts, artifacts: globals.artifacts,
...@@ -179,13 +187,17 @@ class AssembleCommand extends FlutterCommand { ...@@ -179,13 +187,17 @@ class AssembleCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final List<Target> targets = this.targets; final List<Target> targets = createTargets();
final Target target = targets.length == 1 ? targets.single : _CompositeTarget(targets); final Target target = targets.length == 1 ? targets.single : _CompositeTarget(targets);
final BuildResult result = await globals.buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig( final BuildResult result = await globals.buildSystem.build(
resourcePoolSize: argResults.wasParsed('resource-pool-size') target,
? int.tryParse(stringArg('resource-pool-size')) createEnvironment(),
: null, buildSystemConfig: BuildSystemConfig(
)); resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size'))
: null,
),
);
if (!result.success) { if (!result.success) {
for (final ExceptionMeasurement measurement in result.exceptions.values) { for (final ExceptionMeasurement measurement in result.exceptions.values) {
globals.printError('Target ${measurement.target} failed: ${measurement.exception}', globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
......
...@@ -23,7 +23,7 @@ void main() { ...@@ -23,7 +23,7 @@ void main() {
Cache: () => FakeCache(), Cache: () => FakeCache(),
}); });
testbed.test('Can run a build', () async { testbed.test('flutter assemble can run a build', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
return BuildResult(success: true); return BuildResult(success: true);
...@@ -34,7 +34,7 @@ void main() { ...@@ -34,7 +34,7 @@ void main() {
expect(testLogger.traceText, contains('build succeeded.')); expect(testLogger.traceText, contains('build succeeded.'));
}); });
testbed.test('Can parse defines whose values contain =', () async { testbed.test('flutter assemble can parse defines whose values contain =', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
expect((invocation.positionalArguments[1] as Environment).defines, containsPair('FooBar', 'fizz=2')); expect((invocation.positionalArguments[1] as Environment).defines, containsPair('FooBar', 'fizz=2'));
...@@ -46,7 +46,19 @@ void main() { ...@@ -46,7 +46,19 @@ void main() {
expect(testLogger.traceText, contains('build succeeded.')); expect(testLogger.traceText, contains('build succeeded.'));
}); });
testbed.test('Throws ToolExit if not provided with output', () async { testbed.test('flutter assemble can parse inputs', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async {
expect((invocation.positionalArguments[1] as Environment).inputs, containsPair('Foo', 'Bar.txt'));
return BuildResult(success: true);
});
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
await commandRunner.run(<String>['assemble', '-o Output', '-iFoo=Bar.txt', 'debug_macos_bundle_flutter_assets']);
expect(testLogger.traceText, contains('build succeeded.'));
});
testbed.test('flutter assemble throws ToolExit if not provided with output', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
return BuildResult(success: true); return BuildResult(success: true);
...@@ -57,7 +69,7 @@ void main() { ...@@ -57,7 +69,7 @@ void main() {
throwsToolExit()); throwsToolExit());
}); });
testbed.test('Throws ToolExit if called with non-existent rule', () async { testbed.test('flutter assemble throws ToolExit if called with non-existent rule', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
return BuildResult(success: true); return BuildResult(success: true);
...@@ -68,7 +80,7 @@ void main() { ...@@ -68,7 +80,7 @@ void main() {
throwsToolExit()); throwsToolExit());
}); });
testbed.test('Does not log stack traces during build failure', () async { testbed.test('flutter assemble does not log stack traces during build failure', () async {
final StackTrace testStackTrace = StackTrace.current; final StackTrace testStackTrace = StackTrace.current;
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
...@@ -84,7 +96,7 @@ void main() { ...@@ -84,7 +96,7 @@ void main() {
expect(testLogger.errorText, isNot(contains(testStackTrace.toString()))); expect(testLogger.errorText, isNot(contains(testStackTrace.toString())));
}); });
testbed.test('Only writes input and output files when the values change', () async { testbed.test('flutter assemble only writes input and output files when the values change', () async {
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
return BuildResult( return BuildResult(
......
...@@ -375,6 +375,31 @@ void main() { ...@@ -375,6 +375,31 @@ void main() {
expect(environmentA.buildDir.path, isNot(environmentB.buildDir.path)); expect(environmentA.buildDir.path, isNot(environmentB.buildDir.path));
}); });
testWithoutContext('Additional inputs do not change the build configuration', () async {
final Environment environmentA = Environment.test(
fileSystem.currentDirectory,
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
inputs: <String, String>{
'C': 'D',
}
);
final Environment environmentB = Environment.test(
fileSystem.currentDirectory,
artifacts: MockArtifacts(),
processManager: FakeProcessManager.any(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
inputs: <String, String>{
'A': 'B',
}
);
expect(environmentA.buildDir.path, equals(environmentB.buildDir.path));
});
testWithoutContext('A target with depfile dependencies can delete stale outputs on the first run', () async { testWithoutContext('A target with depfile dependencies can delete stale outputs on the first run', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem); final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
int called = 0; int called = 0;
......
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