mirror of
https://github.com/flutter/flutter.git
synced 2026-02-05 03:09:43 +08:00
<!-- start_original_pr_link --> Reverts: flutter/flutter#179647 <!-- end_original_pr_link --> <!-- start_initiating_author --> Initiated by: gmackall <!-- end_initiating_author --> <!-- start_revert_reason --> Reason for reverting: Mac_mokey run_release_test failing <!-- end_revert_reason --> <!-- start_original_pr_author --> Original PR Author: gmackall <!-- end_original_pr_author --> <!-- start_reviewers --> Reviewed By: {reidbaker, jtmcdole} <!-- end_reviewers --> <!-- start_revert_body --> This change reverts the following previous change: Changes our cipd hosted android sdk+ndk bundle to no longer modify directory structure from what Android directly provides, allowing AGP to find the ndk automatically and preventing downloading. 1. Changes the `create_cipd_packages.sh` script so we don't move the ndk 2. Uses the new script to upload a [36v4unmodified](https://chrome-infra-packages.appspot.com/p/flutter/android/sdk/all/linux-amd64/+/version:36v4unmodified) bundle 3. Changes DEPS+ci.yaml to reference the new bundle 4. Changes GN build rules accordingly 5. Removes special config in existing build.gradle files to point at the location of the ndk - agp should now know where it is without conig 6. For some tests, passes in an environment variable pointing to the ndk location, to override the environment variable passed by the recipes. This variable is respected by the tool, so the recipes passing it, pointing to the wrong place, is problematic. After this change lands we can stop passing it in the recipes, and then we can remove this special config in the tests. But adding it temporarily allows us to make this change without coordinating a recipes change at the same moment, and probably breaking things out of band. Logs from a random postsubmit run of `Mac build_android_host_app_with_module_aar` on the tree currently: https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8695664517253382913/+/u/run_build_android_host_app_with_module_aar/stdout We see > [2025-12-12 10:49:49.223087] [STDOUT] stdout: Checking the license for package NDK (Side by side) 28.2.13676358 in /Volumes/Work/s/w/ir/cache/android/sdk/licenses [2025-12-12 10:49:49.223221] [STDOUT] stdout: License for package NDK (Side by side) 28.2.13676358 accepted. [2025-12-12 10:49:49.223268] [STDOUT] stdout: Preparing "Install NDK (Side by side) 28.2.13676358 v.28.2.13676358". [2025-12-12 10:50:14.093824] [STDOUT] stdout: "Install NDK (Side by side) 28.2.13676358 v.28.2.13676358" ready. [2025-12-12 10:50:14.093909] [STDOUT] stdout: Installing NDK (Side by side) 28.2.13676358 in /Volumes/Work/s/w/ir/cache/android/sdk/ndk/28.2.13676358 [2025-12-12 10:50:14.093947] [STDOUT] stdout: "Install NDK (Side by side) 28.2.13676358 v.28.2.13676358" complete. [2025-12-12 10:50:14.410724] [STDOUT] stdout: "Install NDK (Side by side) 28.2.13676358 v.28.2.13676358" finished. Example of no longer downloading: Logs from a presubmit run: https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8695732180931529361/+/u/run_build_android_host_app_with_module_aar/stdout We don't see these logs. We still see android build tools downloading - we can probably tackle that in another pr. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md <!-- end_revert_body --> Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com>
267 lines
8.9 KiB
Dart
267 lines
8.9 KiB
Dart
// Copyright 2014 The Flutter 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 'dart:core' hide print;
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'utils.dart';
|
|
|
|
/// Runs the `executable` and returns standard output as a stream of lines.
|
|
///
|
|
/// The returned stream reaches its end immediately after the command exits.
|
|
///
|
|
/// If `expectNonZeroExit` is false and the process exits with a non-zero exit
|
|
/// code fails the test immediately by exiting the test process with exit code
|
|
/// 1.
|
|
Stream<String> runAndGetStdout(
|
|
String executable,
|
|
List<String> arguments, {
|
|
String? workingDirectory,
|
|
Map<String, String>? environment,
|
|
bool expectNonZeroExit = false,
|
|
}) async* {
|
|
final output = StreamController<String>();
|
|
final Future<CommandResult?> command = runCommand(
|
|
executable,
|
|
arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
expectNonZeroExit: expectNonZeroExit,
|
|
// Capture the output so it's not printed to the console by default.
|
|
outputMode: OutputMode.capture,
|
|
outputListener: (String line, io.Process process) {
|
|
output.add(line);
|
|
},
|
|
);
|
|
|
|
// Close the stream controller after the command is complete. Otherwise,
|
|
// the yield* will never finish.
|
|
command.whenComplete(output.close);
|
|
|
|
yield* output.stream;
|
|
}
|
|
|
|
/// Represents a running process launched using [startCommand].
|
|
class Command {
|
|
Command._(this.process, this._time, this._savedStdout, this._savedStderr);
|
|
|
|
/// The raw process that was launched for this command.
|
|
final io.Process process;
|
|
final Stopwatch _time;
|
|
final Future<String> _savedStdout;
|
|
final Future<String> _savedStderr;
|
|
}
|
|
|
|
/// The result of running a command using [startCommand] and [runCommand];
|
|
class CommandResult {
|
|
CommandResult._(this.exitCode, this.elapsedTime, this.flattenedStdout, this.flattenedStderr);
|
|
|
|
/// The exit code of the process.
|
|
final int exitCode;
|
|
|
|
/// The amount of time it took the process to complete.
|
|
final Duration elapsedTime;
|
|
|
|
/// Standard output decoded as a string using UTF8 decoder.
|
|
final String? flattenedStdout;
|
|
|
|
/// Standard error output decoded as a string using UTF8 decoder.
|
|
final String? flattenedStderr;
|
|
}
|
|
|
|
/// Starts the `executable` and returns a command object representing the
|
|
/// running process.
|
|
///
|
|
/// `outputListener` is called for every line of standard output from the
|
|
/// process, and is given the [Process] object. This can be used to interrupt
|
|
/// an indefinitely running process, for example, by waiting until the process
|
|
/// emits certain output.
|
|
///
|
|
/// `outputMode` controls where the standard output from the command process
|
|
/// goes. See [OutputMode].
|
|
Future<Command> startCommand(
|
|
String executable,
|
|
List<String> arguments, {
|
|
String? workingDirectory,
|
|
Map<String, String>? environment,
|
|
OutputMode outputMode = OutputMode.print,
|
|
bool Function(String)? removeLine,
|
|
void Function(String, io.Process)? outputListener,
|
|
}) async {
|
|
final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
|
|
final commandDescription =
|
|
'${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
print('RUNNING: cd $cyan$relativeWorkingDir$reset; $green$commandDescription$reset');
|
|
|
|
final time = Stopwatch()..start();
|
|
print('workingDirectory: $workingDirectory, executable: $executable, arguments: $arguments');
|
|
final io.Process process = await io.Process.start(
|
|
executable,
|
|
arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
);
|
|
return Command._(
|
|
process,
|
|
time,
|
|
process.stdout
|
|
.transform<String>(const Utf8Decoder())
|
|
.transform(const LineSplitter())
|
|
.where((String line) => removeLine == null || !removeLine(line))
|
|
.map<String>((String line) {
|
|
final formattedLine = '$line\n';
|
|
if (outputListener != null) {
|
|
outputListener(formattedLine, process);
|
|
}
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
print(line);
|
|
case OutputMode.capture:
|
|
break;
|
|
}
|
|
return line;
|
|
})
|
|
.join('\n'),
|
|
process.stderr
|
|
.transform<String>(const Utf8Decoder())
|
|
.transform(const LineSplitter())
|
|
.map<String>((String line) {
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
print(line);
|
|
case OutputMode.capture:
|
|
break;
|
|
}
|
|
return line;
|
|
})
|
|
.join('\n'),
|
|
);
|
|
}
|
|
|
|
/// Runs the `executable` and waits until the process exits.
|
|
///
|
|
/// If the process exits with a non-zero exit code and `expectNonZeroExit` is
|
|
/// false, calls foundError (which does not terminate execution!).
|
|
///
|
|
/// `outputListener` is called for every line of standard output from the
|
|
/// process, and is given the [Process] object. This can be used to interrupt
|
|
/// an indefinitely running process, for example, by waiting until the process
|
|
/// emits certain output.
|
|
///
|
|
/// Returns the result of the finished process.
|
|
///
|
|
/// `outputMode` controls where the standard output from the command process
|
|
/// goes. See [OutputMode].
|
|
Future<CommandResult> runCommand(
|
|
String executable,
|
|
List<String> arguments, {
|
|
String? workingDirectory,
|
|
Map<String, String>? environment,
|
|
bool expectNonZeroExit = false,
|
|
int? expectedExitCode,
|
|
String? failureMessage,
|
|
OutputMode outputMode = OutputMode.print,
|
|
bool Function(String)? removeLine,
|
|
void Function(String, io.Process)? outputListener,
|
|
}) async {
|
|
final commandDescription =
|
|
'${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
|
|
final String relativeWorkingDir = workingDirectory ?? path.relative(io.Directory.current.path);
|
|
if (dryRun) {
|
|
printProgress(_prettyPrintRunCommand(executable, arguments, workingDirectory));
|
|
return CommandResult._(
|
|
0,
|
|
Duration.zero,
|
|
'$executable ${arguments.join(' ')}',
|
|
'Simulated execution due to --dry-run',
|
|
);
|
|
}
|
|
|
|
final Command command = await startCommand(
|
|
executable,
|
|
arguments,
|
|
workingDirectory: workingDirectory,
|
|
environment: environment,
|
|
outputMode: outputMode,
|
|
removeLine: removeLine,
|
|
outputListener: outputListener,
|
|
);
|
|
|
|
final result = CommandResult._(
|
|
await command.process.exitCode,
|
|
command._time.elapsed,
|
|
await command._savedStdout,
|
|
await command._savedStderr,
|
|
);
|
|
|
|
if ((result.exitCode == 0) == expectNonZeroExit ||
|
|
(expectedExitCode != null && result.exitCode != expectedExitCode)) {
|
|
// Print the output when we get unexpected results (unless output was
|
|
// printed already).
|
|
switch (outputMode) {
|
|
case OutputMode.print:
|
|
break;
|
|
case OutputMode.capture:
|
|
print(result.flattenedStdout);
|
|
print(result.flattenedStderr);
|
|
}
|
|
final allOutput = '${result.flattenedStdout}\n${result.flattenedStderr}';
|
|
foundError(<String>[
|
|
?failureMessage,
|
|
'${bold}Command: $green$commandDescription$reset',
|
|
if (failureMessage == null)
|
|
'$bold${red}Command exited with exit code ${result.exitCode} but expected ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'} exit code.$reset',
|
|
'${bold}Working directory: $cyan${path.absolute(relativeWorkingDir)}$reset',
|
|
if (allOutput.isNotEmpty && allOutput.length < 512)
|
|
'${bold}stdout and stderr output:\n$allOutput',
|
|
]);
|
|
} else {
|
|
print(
|
|
'ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset',
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
final String _flutterRoot = path.dirname(
|
|
path.dirname(path.dirname(path.fromUri(io.Platform.script))),
|
|
);
|
|
|
|
String _prettyPrintRunCommand(String executable, List<String> arguments, String? workingDirectory) {
|
|
final output = StringBuffer();
|
|
|
|
// Print CWD relative to the root.
|
|
output.write('|> ');
|
|
output.write(path.relative(executable, from: _flutterRoot));
|
|
if (workingDirectory != null) {
|
|
output.write(' (${path.relative(workingDirectory, from: _flutterRoot)})');
|
|
}
|
|
output.writeln(': ');
|
|
output.writeAll(arguments.map((String a) => ' $a'), '\n');
|
|
|
|
return output.toString();
|
|
}
|
|
|
|
/// Specifies what to do with the command output from [runCommand] and [startCommand].
|
|
enum OutputMode {
|
|
/// Forwards standard output and standard error streams to the test process'
|
|
/// standard output stream (i.e. stderr is redirected to stdout).
|
|
///
|
|
/// Use this mode if all you want is print the output of the command to the
|
|
/// console. The output is no longer available after the process exits.
|
|
print,
|
|
|
|
/// Saves standard output and standard error streams in memory.
|
|
///
|
|
/// Captured output can be retrieved from the [CommandResult] object.
|
|
///
|
|
/// Use this mode in tests that need to inspect the output of a command, or
|
|
/// when the output should not be printed to console.
|
|
capture,
|
|
}
|