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 {
@required ProcessManager processManager,
Directory buildDir,
Map<String, String> defines = const <String, String>{},
Map<String, String> inputs = const <String, String>{},
}) {
// Compute a unique hash of this build's particular environment.
// Sort the keys by key so that the result is stable. We always
......@@ -325,6 +326,7 @@ class Environment {
logger: logger,
artifacts: artifacts,
processManager: processManager,
inputs: inputs,
);
}
......@@ -338,6 +340,7 @@ class Environment {
Directory flutterRootDir,
Directory buildDir,
Map<String, String> defines = const <String, String>{},
Map<String, String> inputs = const <String, String>{},
@required FileSystem fileSystem,
@required Logger logger,
@required Artifacts artifacts,
......@@ -350,6 +353,7 @@ class Environment {
flutterRootDir: flutterRootDir ?? testDirectory,
buildDir: buildDir,
defines: defines,
inputs: inputs,
fileSystem: fileSystem,
logger: logger,
artifacts: artifacts,
......@@ -369,6 +373,7 @@ class Environment {
@required this.logger,
@required this.fileSystem,
@required this.artifacts,
@required this.inputs,
});
/// The [Source] value which is substituted with the path to [projectDir].
......@@ -420,6 +425,16 @@ class Environment {
/// which prevents the config from leaking into different builds.
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.
final Directory rootBuildDir;
......
......@@ -64,6 +64,13 @@ class AssembleCommand extends FlutterCommand {
abbr: 'd',
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. '
'This contains all build inputs and outputs in a make style syntax'
);
......@@ -99,7 +106,7 @@ class AssembleCommand extends FlutterCommand {
return const <CustomDimensions, String>{};
}
try {
final Environment localEnvironment = environment;
final Environment localEnvironment = createEnvironment();
return <CustomDimensions, String>{
CustomDimensions.commandBuildBundleTargetPlatform: localEnvironment.defines['TargetPlatform'],
CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}',
......@@ -111,7 +118,7 @@ class AssembleCommand extends FlutterCommand {
}
/// The target(s) we are building.
List<Target> get targets {
List<Target> createTargets() {
if (argResults.rest.isEmpty) {
throwToolExit('missing target name for flutter assemble.');
}
......@@ -132,7 +139,7 @@ class AssembleCommand extends FlutterCommand {
}
/// The environmental configuration for a build invocation.
Environment get environment {
Environment createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current();
String output = stringArg('output');
if (output == null) {
......@@ -149,6 +156,7 @@ class AssembleCommand extends FlutterCommand {
.childDirectory('flutter_build'),
projectDir: flutterProject.directory,
defines: _parseDefines(stringsArg('define')),
inputs: _parseDefines(stringsArg('input')),
cacheDir: globals.cache.getRoot(),
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
artifacts: globals.artifacts,
......@@ -179,13 +187,17 @@ class AssembleCommand extends FlutterCommand {
@override
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 BuildResult result = await globals.buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size'))
: null,
));
final BuildResult result = await globals.buildSystem.build(
target,
createEnvironment(),
buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size'))
: null,
),
);
if (!result.success) {
for (final ExceptionMeasurement measurement in result.exceptions.values) {
globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
......
......@@ -23,7 +23,7 @@ void main() {
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')))
.thenAnswer((Invocation invocation) async {
return BuildResult(success: true);
......@@ -34,7 +34,7 @@ void main() {
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')))
.thenAnswer((Invocation invocation) async {
expect((invocation.positionalArguments[1] as Environment).defines, containsPair('FooBar', 'fizz=2'));
......@@ -46,7 +46,19 @@ void main() {
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')))
.thenAnswer((Invocation invocation) async {
return BuildResult(success: true);
......@@ -57,7 +69,7 @@ void main() {
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')))
.thenAnswer((Invocation invocation) async {
return BuildResult(success: true);
......@@ -68,7 +80,7 @@ void main() {
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;
when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
.thenAnswer((Invocation invocation) async {
......@@ -84,7 +96,7 @@ void main() {
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')))
.thenAnswer((Invocation invocation) async {
return BuildResult(
......
......@@ -375,6 +375,31 @@ void main() {
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 {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
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