blob: 6a6d3eed19de038392a3991aaf9f5d4c69f73568 [file]
// 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 'package:meta/meta.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../android/android_builder.dart';
import '../android/android_sdk.dart';
import '../android/gradle_utils.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../build_info.dart';
import '../build_system/build_system.dart';
import '../context/android_context.dart';
import '../context/tool_context.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import 'build.dart';
class BuildAarCommand extends BuildSubCommand {
BuildAarCommand({
required this._androidBuilder,
required this._androidContext,
required this._buildSystem,
required ToolContext toolContext,
super.verboseHelp = false,
}) : super(logger: toolContext.logger, toolContext: toolContext) {
enableUsesPubOption();
registerOptionBundles(const <OptionBundle>[
DartCompileOptionsBundle(),
AndroidGradleOptionsBundle(),
]);
argParser.addDescriptors(const <OptionDescriptor<Object?>>[
_debugMode,
_profileMode,
_releaseMode,
CommonOptions.treeShakeIcons,
BuildInfoOptions.flavor,
CommonOptions.buildNumber,
CommonOptions.outputDir,
CommonOptions.pub,
BuildInfoOptions.splitDebugInfo,
BuildInfoOptions.obfuscate,
BuildInfoOptions.extraFrontEndOptions,
BuildInfoOptions.extraGenSnapshotOptions,
_targetPlatform,
BuildInfoOptions.trackWidgetCreation,
], verboseHelp: verboseHelp);
}
static const _debugMode = FlagOptionDescriptor(
name: 'debug',
defaultsTo: true,
help: 'Build a debug version of the current project.',
);
static const _profileMode = FlagOptionDescriptor(
name: 'profile',
defaultsTo: true,
help: 'Build a version of the current project specialized for performance profiling.',
);
static const _releaseMode = FlagOptionDescriptor(
name: 'release',
defaultsTo: true,
help: 'Build a release version of the current project.',
);
static const _targetPlatform = MultiOptionDescriptor(
name: 'target-platform',
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the project is compiled.',
);
final AndroidBuilder _androidBuilder;
final AndroidContext _androidContext;
final BuildSystem _buildSystem;
/// The [AndroidBuilder] used to build the AAR.
@visibleForTesting
AndroidBuilder get androidBuilder => _androidBuilder;
/// The [AndroidContext] containing Android-specific toolchain dependencies.
@visibleForTesting
AndroidContext get androidContext => _androidContext;
/// The [AndroidSdk] instance, if available.
@visibleForTesting
AndroidSdk? get androidSdk => _androidContext.androidSdk;
/// The [BuildSystem] used for building the project.
@visibleForTesting
BuildSystem get buildSystem => _buildSystem;
@override
ToolContext get toolContext => super.toolContext!;
FileSystem get _fileSystem => toolContext.fs;
@override
final name = 'aar';
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.androidGenSnapshot,
};
@override
late final FlutterProject project = _getProject();
@override
Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
final String projectType;
if (project.manifest.isModule) {
projectType = 'module';
} else if (project.manifest.isPlugin) {
projectType = 'plugin';
} else {
projectType = 'app';
}
return Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
buildAarProjectType: projectType,
buildAarTargetPlatform: getValue(_targetPlatform).join(','),
// TODO(gmackall): Consider collecting hcpp analytics, see https://github.com/flutter/flutter/issues/184541.
);
}
@override
final description =
'Build a repository containing an AAR and a POM file.\n\n'
'By default, AARs are built for `release`, `debug` and `profile`.\n'
'The POM file is used to include the dependencies that the AAR was compiled against.\n'
'To learn more about how to use these artifacts, see: https://flutter.dev/to/integrate-android-archive\n'
'This command assumes that the entrypoint is "lib/main.dart". '
'This cannot currently be configured.';
@override
Future<void> validateCommand() async {
if (!project.manifest.isModule) {
throwToolExit('AARs can only be built from modules.');
}
await super.validateCommand();
}
@override
bool get regeneratePlatformSpecificToolingDuringVerify => false;
@override
Future<FlutterCommandResult> runCommand() async {
if (_androidContext.androidSdk == null) {
exitWithNoSdkMessage(analytics: analytics, logger: logger);
}
final androidBuildInfo = <AndroidBuildInfo>{};
final Iterable<CpuArch> targetArchitectures = getValue(_targetPlatform)
.map<CpuArch>(getCpuArchForName);
final String? buildNumberArg = getValue(CommonOptions.buildNumber);
final String buildNumber = (buildNumberArg != null && buildNumberArg.isNotEmpty)
? buildNumberArg
: '1.0';
final File targetFile = _fileSystem.file(_fileSystem.path.join('lib', 'main.dart'));
for (final FlagOptionDescriptor descriptor in const [_debugMode, _profileMode, _releaseMode]) {
if (getValue(descriptor)) {
androidBuildInfo.add(
AndroidBuildInfo(
await getBuildInfo(
forcedBuildMode: BuildMode.fromCliName(descriptor.name),
forcedTargetFile: targetFile,
),
targetArchs: targetArchitectures,
),
);
}
}
if (androidBuildInfo.isEmpty) {
throwToolExit('Please specify a build mode and try again.');
}
await _androidBuilder.buildAar(
project: project,
target: targetFile.path,
androidBuildInfo: androidBuildInfo,
generateTooling: regeneratePlatformSpecificToolingIfApplicable,
outputDirectoryPath: getValue(CommonOptions.outputDir),
buildNumber: buildNumber,
);
final bool impellerEnabled = project.android.computeImpellerEnabled();
final buildLabel = impellerEnabled
? 'manifest-aar-impeller-enabled'
: 'manifest-aar-impeller-disabled';
analytics.send(Event.flutterBuildInfo(label: buildLabel, buildType: 'android'));
return FlutterCommandResult.success();
}
/// Returns the [FlutterProject] which is determined from the remaining command-line
/// argument if any or the current working directory.
FlutterProject _getProject() {
final List<String> remainingArguments = argResults!.rest;
if (remainingArguments.isEmpty) {
return super.project;
}
final File mainFile = _fileSystem.file(remainingArguments.first);
final String path;
if (!mainFile.existsSync()) {
final Directory pathProject = _fileSystem.directory(remainingArguments.first);
if (!pathProject.existsSync()) {
throwToolExit('${remainingArguments.first} does not exist');
}
path = pathProject.path;
} else {
path = mainFile.parent.path;
}
final String? projectRoot = findProjectRoot(_fileSystem, path);
if (projectRoot == null) {
throwToolExit('${mainFile.parent.path} is not a valid flutter project');
}
return toolContext.projectFactory.fromDirectory(_fileSystem.directory(projectRoot));
}
}