mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

`flutter build aar` This new build command works just like `flutter build apk` or `flutter build appbundle`, but for plugin and module projects. This PR also refactors how plugins are included in app or module projects. By building the plugins as AARs, the Android Gradle plugin is able to use Jetifier to translate support libraries into AndroidX libraries for all the plugin's native code. Thus, reducing the error rate when using AndroidX in apps. This change also allows to build modules as AARs, so developers can take these artifacts and distribute them along with the native host app without the need of the Flutter tool. This is a requirement for add to app. `flutter build aar` generates POM artifacts (XML files) which contain metadata about the native dependencies used by the plugin. This allows Gradle to resolve dependencies at the app level. The result of this new build command is a single build/outputs/repo, the local repository that contains all the generated AARs and POM files. In a Flutter app project, this local repo is used by the Flutter Gradle plugin to resolve the plugin dependencies. In add to app case, the developer needs to configure the local repo and the dependency manually in `build.gradle`: repositories { maven { url "<path-to-flutter-module>build/host/outputs/repo" } } dependencies { implementation("<package-name>:flutter_<build-mode>:1.0@aar") { transitive = true } }
718 lines
18 KiB
Dart
718 lines
18 KiB
Dart
// 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 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/context.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/terminal.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/context_runner.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/reporting/usage.dart';
|
|
import 'package:flutter_tools/src/version.dart';
|
|
|
|
import 'context.dart';
|
|
|
|
export 'package:flutter_tools/src/base/context.dart' show Generator;
|
|
|
|
// A default value should be provided if one of the following criteria is met:
|
|
// - The vast majority of tests should use this provider. For example,
|
|
// [BufferLogger], [MemoryFileSystem].
|
|
// - More TBD.
|
|
final Map<Type, Generator> _testbedDefaults = <Type, Generator>{
|
|
// Keeps tests fast by avoid actual file system.
|
|
FileSystem: () => MemoryFileSystem(style: platform.isWindows
|
|
? FileSystemStyle.windows
|
|
: FileSystemStyle.posix),
|
|
Logger: () => BufferLogger(), // Allows reading logs and prevents stdout.
|
|
OperatingSystemUtils: () => FakeOperatingSystemUtils(),
|
|
OutputPreferences: () => OutputPreferences(showColor: false), // configures BufferLogger to avoid color codes.
|
|
Usage: () => NoOpUsage(), // prevent addition of analytics from burdening test mocks
|
|
FlutterVersion: () => FakeFlutterVersion() // prevent requirement to mock git for test runner.
|
|
};
|
|
|
|
/// Manages interaction with the tool injection and runner system.
|
|
///
|
|
/// The Testbed automatically injects reasonable defaults through the context
|
|
/// DI system such as a [BufferLogger] and a [MemoryFileSytem].
|
|
///
|
|
/// Example:
|
|
///
|
|
/// Testing that a filesystem operation works as expected
|
|
///
|
|
/// void main() {
|
|
/// group('Example', () {
|
|
/// Testbed testbed;
|
|
///
|
|
/// setUp(() {
|
|
/// testbed = Testbed(setUp: () {
|
|
/// fs.file('foo').createSync()
|
|
/// });
|
|
/// })
|
|
///
|
|
/// test('Can delete a file', () => testBed.run(() {
|
|
/// expect(fs.file('foo').existsSync(), true);
|
|
/// fs.file('foo').deleteSync();
|
|
/// expect(fs.file('foo').existsSync(), false);
|
|
/// }));
|
|
/// });
|
|
/// }
|
|
///
|
|
/// For a more detailed example, see the code in test_compiler_test.dart.
|
|
class Testbed {
|
|
/// Creates a new [TestBed]
|
|
///
|
|
/// `overrides` provides more overrides in addition to the test defaults.
|
|
/// `setup` may be provided to apply mocks within the tool managed zone,
|
|
/// including any specified overrides.
|
|
Testbed({FutureOr<void> Function() setup, Map<Type, Generator> overrides})
|
|
: _setup = setup,
|
|
_overrides = overrides;
|
|
|
|
final FutureOr<void> Function() _setup;
|
|
final Map<Type, Generator> _overrides;
|
|
|
|
/// Runs `test` within a tool zone.
|
|
///
|
|
/// `overrides` may be used to provide new context values for the single test
|
|
/// case or override any context values from the setup.
|
|
FutureOr<T> run<T>(FutureOr<T> Function() test, {Map<Type, Generator> overrides}) {
|
|
final Map<Type, Generator> testOverrides = <Type, Generator>{
|
|
..._testbedDefaults,
|
|
// Add the initial setUp overrides
|
|
...?_overrides,
|
|
// Add the test-specific overrides
|
|
...?overrides,
|
|
};
|
|
// Cache the original flutter root to restore after the test case.
|
|
final String originalFlutterRoot = Cache.flutterRoot;
|
|
// Track pending timers to verify that they were correctly cleaned up.
|
|
final Map<Timer, StackTrace> timers = <Timer, StackTrace>{};
|
|
|
|
return HttpOverrides.runZoned(() {
|
|
return runInContext<T>(() {
|
|
return context.run<T>(
|
|
name: 'testbed',
|
|
overrides: testOverrides,
|
|
zoneSpecification: ZoneSpecification(
|
|
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() timer) {
|
|
final Timer result = parent.createTimer(zone, duration, timer);
|
|
timers[result] = StackTrace.current;
|
|
return result;
|
|
},
|
|
createPeriodicTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration period, void Function(Timer) timer) {
|
|
final Timer result = parent.createPeriodicTimer(zone, period, timer);
|
|
timers[result] = StackTrace.current;
|
|
return result;
|
|
}
|
|
),
|
|
body: () async {
|
|
Cache.flutterRoot = '';
|
|
if (_setup != null) {
|
|
await _setup();
|
|
}
|
|
await test();
|
|
Cache.flutterRoot = originalFlutterRoot;
|
|
for (MapEntry<Timer, StackTrace> entry in timers.entries) {
|
|
if (entry.key.isActive) {
|
|
throw StateError('A Timer was active at the end of a test: ${entry.value}');
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
});
|
|
}, createHttpClient: (SecurityContext c) => FakeHttpClient());
|
|
}
|
|
}
|
|
|
|
/// A no-op implementation of [Usage] for testing.
|
|
class NoOpUsage implements Usage {
|
|
@override
|
|
bool enabled = false;
|
|
|
|
@override
|
|
bool suppressAnalytics = true;
|
|
|
|
@override
|
|
String get clientId => 'test';
|
|
|
|
@override
|
|
Future<void> ensureAnalyticsSent() {
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
bool get isFirstRun => false;
|
|
|
|
@override
|
|
Stream<Map<String, Object>> get onSend => const Stream<Object>.empty();
|
|
|
|
@override
|
|
void printWelcome() {}
|
|
|
|
@override
|
|
void sendCommand(String command, {Map<String, String> parameters}) {}
|
|
|
|
@override
|
|
void sendEvent(String category, String parameter,{ Map<String, String> parameters }) {}
|
|
|
|
@override
|
|
void sendException(dynamic exception) {}
|
|
|
|
@override
|
|
void sendTiming(String category, String variableName, Duration duration, { String label }) {}
|
|
}
|
|
|
|
class FakeHttpClient implements HttpClient {
|
|
@override
|
|
bool autoUncompress;
|
|
|
|
@override
|
|
Duration connectionTimeout;
|
|
|
|
@override
|
|
Duration idleTimeout;
|
|
|
|
@override
|
|
int maxConnectionsPerHost;
|
|
|
|
@override
|
|
String userAgent;
|
|
|
|
@override
|
|
void addCredentials(
|
|
Uri url, String realm, HttpClientCredentials credentials) {}
|
|
|
|
@override
|
|
void addProxyCredentials(
|
|
String host, int port, String realm, HttpClientCredentials credentials) {}
|
|
|
|
@override
|
|
set authenticate(
|
|
Future<bool> Function(Uri url, String scheme, String realm) f) {}
|
|
|
|
@override
|
|
set authenticateProxy(
|
|
Future<bool> Function(String host, int port, String scheme, String realm)
|
|
f) {}
|
|
|
|
@override
|
|
set badCertificateCallback(
|
|
bool Function(X509Certificate cert, String host, int port) callback) {}
|
|
|
|
@override
|
|
void close({bool force = false}) {}
|
|
|
|
@override
|
|
Future<HttpClientRequest> delete(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> deleteUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
set findProxy(String Function(Uri url) f) {}
|
|
|
|
@override
|
|
Future<HttpClientRequest> get(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> getUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> head(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> headUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> open(String method, String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> openUrl(String method, Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> patch(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> patchUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> post(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> postUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> put(String host, int port, String path) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
|
|
@override
|
|
Future<HttpClientRequest> putUrl(Uri url) async {
|
|
return FakeHttpClientRequest();
|
|
}
|
|
}
|
|
|
|
class FakeHttpClientRequest implements HttpClientRequest {
|
|
FakeHttpClientRequest();
|
|
|
|
@override
|
|
bool bufferOutput;
|
|
|
|
@override
|
|
int contentLength;
|
|
|
|
@override
|
|
Encoding encoding;
|
|
|
|
@override
|
|
bool followRedirects;
|
|
|
|
@override
|
|
int maxRedirects;
|
|
|
|
@override
|
|
bool persistentConnection;
|
|
|
|
@override
|
|
void add(List<int> data) {}
|
|
|
|
@override
|
|
void addError(Object error, [StackTrace stackTrace]) {}
|
|
|
|
@override
|
|
Future<void> addStream(Stream<List<int>> stream) async {}
|
|
|
|
@override
|
|
Future<HttpClientResponse> close() async {
|
|
return FakeHttpClientResponse();
|
|
}
|
|
|
|
@override
|
|
HttpConnectionInfo get connectionInfo => null;
|
|
|
|
@override
|
|
List<Cookie> get cookies => <Cookie>[];
|
|
|
|
@override
|
|
Future<HttpClientResponse> get done => null;
|
|
|
|
@override
|
|
Future<void> flush() {
|
|
return Future<void>.value();
|
|
}
|
|
|
|
@override
|
|
HttpHeaders get headers => null;
|
|
|
|
@override
|
|
String get method => null;
|
|
|
|
@override
|
|
Uri get uri => null;
|
|
|
|
@override
|
|
void write(Object obj) {}
|
|
|
|
@override
|
|
void writeAll(Iterable<Object> objects, [String separator = '']) {}
|
|
|
|
@override
|
|
void writeCharCode(int charCode) {}
|
|
|
|
@override
|
|
void writeln([Object obj = '']) {}
|
|
}
|
|
|
|
class FakeHttpClientResponse implements HttpClientResponse {
|
|
final Stream<Uint8List> _delegate = Stream<Uint8List>.fromIterable(const Iterable<Uint8List>.empty());
|
|
|
|
@override
|
|
final HttpHeaders headers = FakeHttpHeaders();
|
|
|
|
@override
|
|
X509Certificate get certificate => null;
|
|
|
|
@override
|
|
HttpConnectionInfo get connectionInfo => null;
|
|
|
|
@override
|
|
int get contentLength => 0;
|
|
|
|
@override
|
|
HttpClientResponseCompressionState get compressionState {
|
|
return HttpClientResponseCompressionState.decompressed;
|
|
}
|
|
|
|
@override
|
|
List<Cookie> get cookies => null;
|
|
|
|
@override
|
|
Future<Socket> detachSocket() {
|
|
return Future<Socket>.error(UnsupportedError('Mocked response'));
|
|
}
|
|
|
|
@override
|
|
bool get isRedirect => false;
|
|
|
|
@override
|
|
StreamSubscription<Uint8List> listen(void Function(Uint8List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) {
|
|
return const Stream<Uint8List>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
|
}
|
|
|
|
@override
|
|
bool get persistentConnection => null;
|
|
|
|
@override
|
|
String get reasonPhrase => null;
|
|
|
|
@override
|
|
Future<HttpClientResponse> redirect([ String method, Uri url, bool followLoops ]) {
|
|
return Future<HttpClientResponse>.error(UnsupportedError('Mocked response'));
|
|
}
|
|
|
|
@override
|
|
List<RedirectInfo> get redirects => <RedirectInfo>[];
|
|
|
|
@override
|
|
int get statusCode => 400;
|
|
|
|
@override
|
|
Future<bool> any(bool Function(Uint8List element) test) {
|
|
return _delegate.any(test);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> asBroadcastStream({
|
|
void Function(StreamSubscription<Uint8List> subscription) onListen,
|
|
void Function(StreamSubscription<Uint8List> subscription) onCancel,
|
|
}) {
|
|
return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel);
|
|
}
|
|
|
|
@override
|
|
Stream<E> asyncExpand<E>(Stream<E> Function(Uint8List event) convert) {
|
|
return _delegate.asyncExpand<E>(convert);
|
|
}
|
|
|
|
@override
|
|
Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) {
|
|
return _delegate.asyncMap<E>(convert);
|
|
}
|
|
|
|
@override
|
|
Stream<R> cast<R>() {
|
|
return _delegate.cast<R>();
|
|
}
|
|
|
|
@override
|
|
Future<bool> contains(Object needle) {
|
|
return _delegate.contains(needle);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> distinct([bool Function(Uint8List previous, Uint8List next) equals]) {
|
|
return _delegate.distinct(equals);
|
|
}
|
|
|
|
@override
|
|
Future<E> drain<E>([E futureValue]) {
|
|
return _delegate.drain<E>(futureValue);
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> elementAt(int index) {
|
|
return _delegate.elementAt(index);
|
|
}
|
|
|
|
@override
|
|
Future<bool> every(bool Function(Uint8List element) test) {
|
|
return _delegate.every(test);
|
|
}
|
|
|
|
@override
|
|
Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) {
|
|
return _delegate.expand(convert);
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> get first => _delegate.first;
|
|
|
|
@override
|
|
Future<Uint8List> firstWhere(
|
|
bool Function(Uint8List element) test, {
|
|
List<int> Function() orElse,
|
|
}) {
|
|
return _delegate.firstWhere(test, orElse: orElse);
|
|
}
|
|
|
|
@override
|
|
Future<S> fold<S>(S initialValue, S Function(S previous, Uint8List element) combine) {
|
|
return _delegate.fold<S>(initialValue, combine);
|
|
}
|
|
|
|
@override
|
|
Future<dynamic> forEach(void Function(Uint8List element) action) {
|
|
return _delegate.forEach(action);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> handleError(
|
|
Function onError, {
|
|
bool Function(dynamic error) test,
|
|
}) {
|
|
return _delegate.handleError(onError, test: test);
|
|
}
|
|
|
|
@override
|
|
bool get isBroadcast => _delegate.isBroadcast;
|
|
|
|
@override
|
|
Future<bool> get isEmpty => _delegate.isEmpty;
|
|
|
|
@override
|
|
Future<String> join([String separator = '']) {
|
|
return _delegate.join(separator);
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> get last => _delegate.last;
|
|
|
|
@override
|
|
Future<Uint8List> lastWhere(
|
|
bool Function(Uint8List element) test, {
|
|
List<int> Function() orElse,
|
|
}) {
|
|
return _delegate.lastWhere(test, orElse: orElse);
|
|
}
|
|
|
|
@override
|
|
Future<int> get length => _delegate.length;
|
|
|
|
@override
|
|
Stream<S> map<S>(S Function(Uint8List event) convert) {
|
|
return _delegate.map<S>(convert);
|
|
}
|
|
|
|
@override
|
|
Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) {
|
|
return _delegate.pipe(streamConsumer);
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> reduce(List<int> Function(Uint8List previous, Uint8List element) combine) {
|
|
return _delegate.reduce(combine);
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> get single => _delegate.single;
|
|
|
|
@override
|
|
Future<Uint8List> singleWhere(bool Function(Uint8List element) test, {List<int> Function() orElse}) {
|
|
return _delegate.singleWhere(test, orElse: orElse);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> skip(int count) {
|
|
return _delegate.skip(count);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) {
|
|
return _delegate.skipWhile(test);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> take(int count) {
|
|
return _delegate.take(count);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) {
|
|
return _delegate.takeWhile(test);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> timeout(
|
|
Duration timeLimit, {
|
|
void Function(EventSink<Uint8List> sink) onTimeout,
|
|
}) {
|
|
return _delegate.timeout(timeLimit, onTimeout: onTimeout);
|
|
}
|
|
|
|
@override
|
|
Future<List<Uint8List>> toList() {
|
|
return _delegate.toList();
|
|
}
|
|
|
|
@override
|
|
Future<Set<Uint8List>> toSet() {
|
|
return _delegate.toSet();
|
|
}
|
|
|
|
@override
|
|
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
|
|
return _delegate.transform<S>(streamTransformer);
|
|
}
|
|
|
|
@override
|
|
Stream<Uint8List> where(bool Function(Uint8List event) test) {
|
|
return _delegate.where(test);
|
|
}
|
|
}
|
|
|
|
/// A fake [HttpHeaders] that ignores all writes.
|
|
class FakeHttpHeaders extends HttpHeaders {
|
|
@override
|
|
List<String> operator [](String name) => <String>[];
|
|
|
|
@override
|
|
void add(String name, Object value) { }
|
|
|
|
@override
|
|
void clear() { }
|
|
|
|
@override
|
|
void forEach(void Function(String name, List<String> values) f) { }
|
|
|
|
@override
|
|
void noFolding(String name) { }
|
|
|
|
@override
|
|
void remove(String name, Object value) { }
|
|
|
|
@override
|
|
void removeAll(String name) { }
|
|
|
|
@override
|
|
void set(String name, Object value) { }
|
|
|
|
@override
|
|
String value(String name) => null;
|
|
}
|
|
|
|
class FakeFlutterVersion implements FlutterVersion {
|
|
@override
|
|
String get channel => 'master';
|
|
|
|
@override
|
|
Future<void> checkFlutterVersionFreshness() async { }
|
|
|
|
@override
|
|
bool checkRevisionAncestry({String tentativeDescendantRevision, String tentativeAncestorRevision}) {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
@override
|
|
String get dartSdkVersion => '12';
|
|
|
|
@override
|
|
String get engineRevision => '42.2';
|
|
|
|
@override
|
|
String get engineRevisionShort => '42';
|
|
|
|
@override
|
|
Future<void> ensureVersionFile() async { }
|
|
|
|
@override
|
|
String get frameworkAge => null;
|
|
|
|
@override
|
|
String get frameworkCommitDate => null;
|
|
|
|
@override
|
|
String get frameworkDate => null;
|
|
|
|
@override
|
|
String get frameworkRevision => null;
|
|
|
|
@override
|
|
String get frameworkRevisionShort => null;
|
|
|
|
@override
|
|
String get frameworkVersion => null;
|
|
|
|
@override
|
|
String getBranchName({bool redactUnknownBranches = false}) {
|
|
return 'master';
|
|
}
|
|
|
|
@override
|
|
String getVersionString({bool redactUnknownBranches = false}) {
|
|
return 'v0.0.0';
|
|
}
|
|
|
|
@override
|
|
bool get isMaster => true;
|
|
|
|
@override
|
|
String get repositoryUrl => null;
|
|
|
|
@override
|
|
Map<String, Object> toJson() {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// A test implementation of [FeatureFlags] that allows enabling without reading
|
|
// config. If not otherwise specified, all values default to false.
|
|
class TestFeatureFlags implements FeatureFlags {
|
|
TestFeatureFlags({
|
|
this.isLinuxEnabled = false,
|
|
this.isMacOSEnabled = false,
|
|
this.isWebEnabled = false,
|
|
this.isWindowsEnabled = false,
|
|
this.isPluginAsAarEnabled = false,
|
|
});
|
|
|
|
@override
|
|
final bool isLinuxEnabled;
|
|
|
|
@override
|
|
final bool isMacOSEnabled;
|
|
|
|
@override
|
|
final bool isWebEnabled;
|
|
|
|
@override
|
|
final bool isWindowsEnabled;
|
|
|
|
@override
|
|
final bool isPluginAsAarEnabled;
|
|
}
|