| // 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. |
| |
| /// @docImport 'build_system/build_system.dart'; |
| library; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'package:package_config/package_config_types.dart'; |
| |
| import 'artifacts.dart'; |
| import 'base/config.dart'; |
| import 'base/file_system.dart'; |
| import 'base/logger.dart'; |
| import 'base/os.dart'; |
| import 'base/utils.dart'; |
| import 'convert.dart'; |
| import 'darwin/darwin.dart'; |
| import 'globals.dart' as globals; |
| import 'runner/flutter_command.dart' show FlutterOptions; |
| |
| /// Whether icon font subsetting is enabled by default. |
| const kIconTreeShakerEnabledDefault = true; |
| |
| /// Information about a build to be performed or used. |
| class BuildInfo { |
| const BuildInfo( |
| this.mode, |
| this.flavor, { |
| this.trackWidgetCreation = false, |
| this.frontendServerStarterPath, |
| List<String>? extraFrontEndOptions, |
| List<String>? extraGenSnapshotOptions, |
| List<String>? fileSystemRoots, |
| this.androidProjectArgs = const <String>[], |
| this.androidGradleProjectCacheDir, |
| this.fileSystemScheme, |
| this.buildNumber, |
| this.buildName, |
| this.splitDebugInfoPath, |
| this.dartObfuscation = false, |
| List<String>? dartDefines, |
| List<String>? dartExperiments, |
| required this.treeShakeIcons, |
| this.performanceMeasurementFile, |
| required this.packageConfigPath, |
| this.codeSizeDirectory, |
| this.androidGradleDaemon = true, |
| this.androidSkipBuildDependencyValidation = false, |
| this.packageConfig = PackageConfig.empty, |
| this.initializeFromDill, |
| this.assumeInitializeFromDillUpToDate = false, |
| this.buildNativeAssets = true, |
| this.useLocalCanvasKit = false, |
| this.webEnableHotReload = false, |
| }) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[], |
| extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[], |
| fileSystemRoots = fileSystemRoots ?? const <String>[], |
| dartDefines = dartDefines ?? const <String>[], |
| dartExperiments = dartExperiments ?? const <String>[]; |
| |
| final BuildMode mode; |
| |
| /// Whether the build should subset icon fonts. |
| final bool treeShakeIcons; |
| |
| /// Represents a custom Android product flavor or an Xcode scheme, null for |
| /// using the default. |
| /// |
| /// If not null, the Gradle build task will be `assembleFlavorMode` (e.g. |
| /// `assemblePaidRelease`), and the Xcode build configuration will be |
| /// Mode-Flavor (e.g. Release-Paid). |
| final String? flavor; |
| |
| /// The path to the package configuration file to use for compilation. |
| /// |
| /// This is used by package:package_config to locate the actual package_config.json |
| /// file. If not provided, defaults to `.dart_tool/package_config.json`. |
| final String packageConfigPath; |
| |
| final List<String> fileSystemRoots; |
| final String? fileSystemScheme; |
| |
| /// Whether the build should track widget creation locations. |
| final bool trackWidgetCreation; |
| |
| /// If provided, the frontend server will be started in JIT mode from this |
| /// file. |
| final String? frontendServerStarterPath; |
| |
| /// Extra command-line options for front-end. |
| final List<String> extraFrontEndOptions; |
| |
| /// Extra command-line options for gen_snapshot. |
| final List<String> extraGenSnapshotOptions; |
| |
| /// Internal version number (not displayed to users). |
| /// Each build must have a unique number to differentiate it from previous builds. |
| /// It is used to determine whether one build is more recent than another, with higher numbers indicating more recent build. |
| /// On Android it is used as versionCode. |
| /// On Xcode builds it is used as CFBundleVersion. |
| /// On Windows it is used as the build suffix for the product and file versions. |
| final String? buildNumber; |
| |
| /// A "x.y.z" string used as the version number shown to users. |
| /// For each new version of your app, you will provide a version number to differentiate it from previous versions. |
| /// On Android it is used as versionName. |
| /// On Xcode builds it is used as CFBundleShortVersionString. |
| /// On Windows it is used as the major, minor, and patch parts of the product and file versions. |
| final String? buildName; |
| |
| /// An optional directory path to save debugging information from dwarf stack |
| /// traces. If null, stack trace information is not stripped from the |
| /// executable. |
| final String? splitDebugInfoPath; |
| |
| /// Whether to apply dart source code obfuscation. |
| final bool dartObfuscation; |
| |
| /// Additional constant values to be made available in the Dart program. |
| /// |
| /// These values can be used with the const `fromEnvironment` constructors of |
| /// [bool], [String], [int], and [double]. |
| final List<String> dartDefines; |
| |
| /// A list of Dart experiments. |
| final List<String> dartExperiments; |
| |
| /// The name of a file where flutter assemble will output performance |
| /// information in a JSON format. |
| /// |
| /// This is not considered a build input and will not force assemble to |
| /// rerun tasks. |
| final String? performanceMeasurementFile; |
| |
| /// If provided, an output directory where one or more v8-style heap snapshots |
| /// will be written for code size profiling. |
| final String? codeSizeDirectory; |
| |
| /// Whether to enable the Gradle daemon when performing an Android build. |
| /// |
| /// Starting the daemon is the default behavior of the gradle wrapper script created |
| /// in a Flutter project. Setting this value to false will cause the tool to pass |
| /// `--no-daemon` to the gradle wrapper script, preventing it from spawning a daemon |
| /// process. |
| /// |
| /// For one-off builds or CI systems, preventing the daemon from spawning will |
| /// reduce system resource usage, at the cost of any subsequent builds starting |
| /// up slightly slower. |
| /// |
| /// The Gradle daemon may also be disabled in the Android application's properties file. |
| final bool androidGradleDaemon; |
| |
| /// Whether to skip checking of individual versions of our Android build time |
| /// dependencies. |
| final bool androidSkipBuildDependencyValidation; |
| |
| /// Additional key value pairs that are passed directly to the gradle project via the `-P` |
| /// flag. |
| final List<String> androidProjectArgs; |
| |
| /// Specifies Gradle's project-specific cache directory. |
| final String? androidGradleProjectCacheDir; |
| |
| /// The package configuration for the loaded application. |
| /// |
| /// This is captured once during startup, but the actual package configuration |
| /// may change during a 'flutter run` workflow. |
| final PackageConfig packageConfig; |
| |
| /// The kernel file that the resident compiler will be initialized with. |
| /// |
| /// If this is null, it will be initialized from the default cached location. |
| final String? initializeFromDill; |
| |
| /// If set, assumes that the file passed in [initializeFromDill] is up to date |
| /// and skips the check and potential invalidation of files. |
| final bool assumeInitializeFromDillUpToDate; |
| |
| /// If set, builds native assets with `build.dart` from all packages. |
| final bool buildNativeAssets; |
| |
| /// If set, web builds will use the locally built CanvasKit instead of using the CDN |
| final bool useLocalCanvasKit; |
| |
| /// If set, web builds with DDC will run with support for hot reload. |
| final bool webEnableHotReload; |
| |
| /// Can be used when the actual information is not needed. |
| static const dummy = BuildInfo( |
| BuildMode.debug, |
| null, |
| trackWidgetCreation: true, |
| treeShakeIcons: false, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| @visibleForTesting |
| static const debug = BuildInfo( |
| BuildMode.debug, |
| null, |
| trackWidgetCreation: true, |
| treeShakeIcons: false, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| @visibleForTesting |
| static const profile = BuildInfo( |
| BuildMode.profile, |
| null, |
| treeShakeIcons: kIconTreeShakerEnabledDefault, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| @visibleForTesting |
| static const jitRelease = BuildInfo( |
| BuildMode.jitRelease, |
| null, |
| treeShakeIcons: kIconTreeShakerEnabledDefault, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| @visibleForTesting |
| static const release = BuildInfo( |
| BuildMode.release, |
| null, |
| treeShakeIcons: kIconTreeShakerEnabledDefault, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| /// Returns whether a debug build is requested. |
| /// |
| /// Exactly one of [isDebug], [isProfile], or [isRelease] is true. |
| bool get isDebug => mode == BuildMode.debug; |
| |
| /// Returns whether a profile build is requested. |
| /// |
| /// Exactly one of [isDebug], [isProfile], [isJitRelease], |
| /// or [isRelease] is true. |
| bool get isProfile => mode == BuildMode.profile; |
| |
| /// Returns whether a release build is requested. |
| /// |
| /// Exactly one of [isDebug], [isProfile], [isJitRelease], |
| /// or [isRelease] is true. |
| bool get isRelease => mode == BuildMode.release; |
| |
| /// Returns whether a JIT release build is requested. |
| /// |
| /// Exactly one of [isDebug], [isProfile], [isJitRelease], |
| /// or [isRelease] is true. |
| bool get isJitRelease => mode == BuildMode.jitRelease; |
| |
| bool get usesAot => isAotBuildMode(mode); |
| bool get supportsEmulator => isEmulatorBuildMode(mode); |
| bool get supportsSimulator => isEmulatorBuildMode(mode); |
| String get modeName => mode.cliName; |
| |
| /// the flavor name in the output apk files is lower-cased (see Flutter Gradle Plugin), |
| /// so the lower cased flavor name is used to compute the output file name |
| String? get lowerCasedFlavor => flavor?.toLowerCase(); |
| |
| /// the flavor name in the output bundle files has the first character lower-cased, |
| /// so the uncapitalized flavor name is used to compute the output file name |
| String? get uncapitalizedFlavor => _uncapitalize(flavor); |
| |
| /// The module system DDC is targeting, or null if not using DDC or the |
| /// associated flag isn't present. |
| // TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060. |
| DdcModuleFormat get ddcModuleFormat { |
| final DdcModuleFormat moduleFormat = webEnableHotReload |
| ? DdcModuleFormat.ddc |
| : DdcModuleFormat.amd; |
| final DdcModuleFormat? parsedFormat = _ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs( |
| extraFrontEndOptions, |
| ).ddcModuleFormat; |
| if (parsedFormat != null && moduleFormat != parsedFormat) { |
| throw Exception( |
| 'Unsupported option combination:\n' |
| '${FlutterOptions.kWebExperimentalHotReload}: $webEnableHotReload\n' |
| '${FlutterOptions.kExtraFrontEndOptions}: --dartdevc-module-format=${parsedFormat.name}', |
| ); |
| } |
| return moduleFormat; |
| } |
| |
| /// Whether to enable canary features when using DDC, or null if not using |
| /// DDC or the associated flag isn't present. |
| bool get canaryFeatures { |
| final bool canaryEnabled = webEnableHotReload; |
| final bool? parsedCanary = _ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs( |
| extraFrontEndOptions, |
| ).canaryFeatures; |
| if (parsedCanary != null && canaryEnabled != parsedCanary) { |
| throw Exception( |
| 'Unsupported option combination:\n' |
| '${FlutterOptions.kWebExperimentalHotReload}: $webEnableHotReload\n' |
| '${FlutterOptions.kExtraFrontEndOptions}: --dartdevc-canary=$parsedCanary', |
| ); |
| } |
| return canaryEnabled; |
| } |
| |
| /// Convert to a structured string encoded structure appropriate for usage |
| /// in build system [Environment.defines]. |
| /// |
| /// Fields that are `null` are excluded from this configuration. |
| Map<String, String> toBuildSystemEnvironment() { |
| // packagesPath and performanceMeasurementFile are not passed into |
| // the Environment map. |
| return <String, String>{ |
| kBuildMode: mode.cliName, |
| if (dartDefines.isNotEmpty) kDartDefines: encodeDartDefines(dartDefines), |
| kDartObfuscation: dartObfuscation.toString(), |
| kFrontendServerStarterPath: ?frontendServerStarterPath, |
| if (extraFrontEndOptions.isNotEmpty) kExtraFrontEndOptions: extraFrontEndOptions.join(','), |
| if (extraGenSnapshotOptions.isNotEmpty) |
| kExtraGenSnapshotOptions: extraGenSnapshotOptions.join(','), |
| kSplitDebugInfo: ?splitDebugInfoPath, |
| kTrackWidgetCreation: trackWidgetCreation.toString(), |
| kIconTreeShakerFlag: treeShakeIcons.toString(), |
| kCodeSizeDirectory: ?codeSizeDirectory, |
| if (fileSystemRoots.isNotEmpty) kFileSystemRoots: fileSystemRoots.join(','), |
| kFileSystemScheme: ?fileSystemScheme, |
| kBuildName: ?buildName, |
| kBuildNumber: ?buildNumber, |
| if (useLocalCanvasKit) kUseLocalCanvasKitFlag: useLocalCanvasKit.toString(), |
| }; |
| } |
| |
| /// Convert to a structured string encoded structure appropriate for usage as |
| /// environment variables or to embed in other scripts. |
| /// |
| /// Fields that are `null` are excluded from this configuration. |
| Map<String, String> toEnvironmentConfig() { |
| return <String, String>{ |
| if (dartDefines.isNotEmpty) 'DART_DEFINES': encodeDartDefines(dartDefines), |
| 'DART_OBFUSCATION': dartObfuscation.toString(), |
| if (frontendServerStarterPath != null) |
| 'FRONTEND_SERVER_STARTER_PATH': frontendServerStarterPath!, |
| if (extraFrontEndOptions.isNotEmpty) |
| 'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','), |
| if (extraGenSnapshotOptions.isNotEmpty) |
| 'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','), |
| 'SPLIT_DEBUG_INFO': ?splitDebugInfoPath, |
| 'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(), |
| 'TREE_SHAKE_ICONS': treeShakeIcons.toString(), |
| if (performanceMeasurementFile != null) |
| 'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile!, |
| 'PACKAGE_CONFIG': packageConfigPath, |
| 'CODE_SIZE_DIRECTORY': ?codeSizeDirectory, |
| 'FLAVOR': ?flavor, |
| }; |
| } |
| |
| /// Convert this config to a series of project level arguments to be passed |
| /// on the command line to gradle. |
| List<String> toGradleConfig() { |
| // PACKAGE_CONFIG not currently supported. |
| return <String>[ |
| if (dartDefines.isNotEmpty) '-Pdart-defines=${encodeDartDefines(dartDefines)}', |
| '-Pdart-obfuscation=$dartObfuscation', |
| if (frontendServerStarterPath != null) |
| '-Pfrontend-server-starter-path=$frontendServerStarterPath', |
| if (extraFrontEndOptions.isNotEmpty) |
| '-Pextra-front-end-options=${extraFrontEndOptions.join(',')}', |
| if (extraGenSnapshotOptions.isNotEmpty) |
| '-Pextra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}', |
| if (splitDebugInfoPath != null) '-Psplit-debug-info=$splitDebugInfoPath', |
| '-Ptrack-widget-creation=$trackWidgetCreation', |
| '-Ptree-shake-icons=$treeShakeIcons', |
| if (performanceMeasurementFile != null) |
| '-Pperformance-measurement-file=$performanceMeasurementFile', |
| if (codeSizeDirectory != null) '-Pcode-size-directory=$codeSizeDirectory', |
| for (final String projectArg in androidProjectArgs) '-P$projectArg', |
| if (androidGradleProjectCacheDir != null) '--project-cache-dir=$androidGradleProjectCacheDir', |
| ]; |
| } |
| } |
| |
| /// Information about an Android build to be performed or used. |
| class AndroidBuildInfo { |
| const AndroidBuildInfo( |
| this.buildInfo, { |
| this.targetArchs = const <AndroidArch>[ |
| AndroidArch.armeabi_v7a, |
| AndroidArch.arm64_v8a, |
| AndroidArch.x86_64, |
| ], |
| this.splitPerAbi = false, |
| this.fastStart = false, |
| }); |
| |
| // The build info containing the mode and flavor. |
| final BuildInfo buildInfo; |
| |
| /// Whether to split the shared library per ABI. |
| /// |
| /// When this is false, multiple ABIs will be contained within one primary |
| /// build artifact. When this is true, multiple build artifacts (one per ABI) |
| /// will be produced. |
| final bool splitPerAbi; |
| |
| /// The target platforms for the build. |
| final Iterable<AndroidArch> targetArchs; |
| |
| /// Whether to bootstrap an empty application. |
| final bool fastStart; |
| } |
| |
| /// A summary of the compilation strategy used for Dart. |
| enum BuildMode { |
| /// Built in JIT mode with no optimizations, enabled asserts, and a VM service. |
| debug, |
| |
| /// Built in AOT mode with some optimizations and a VM service. |
| profile, |
| |
| /// Built in AOT mode with all optimizations and no VM service. |
| release, |
| |
| /// Built in JIT mode with all optimizations and no VM service. |
| jitRelease; |
| |
| factory BuildMode.fromCliName(String value) => values.singleWhere( |
| (BuildMode element) => element.cliName == value, |
| orElse: () => throw ArgumentError('$value is not a supported build mode'), |
| ); |
| |
| static const releaseModes = <BuildMode>{release, jitRelease}; |
| static const jitModes = <BuildMode>{debug, jitRelease}; |
| |
| /// Whether this mode is considered release. |
| /// |
| /// Useful for determining whether we should enable/disable asserts or |
| /// other development features. |
| bool get isRelease => releaseModes.contains(this); |
| |
| /// Whether this mode is using the JIT runtime. |
| bool get isJit => jitModes.contains(this); |
| |
| /// Whether this mode is using the precompiled runtime. |
| bool get isPrecompiled => !isJit; |
| |
| /// [name] formatted in snake case. |
| /// |
| /// (e.g. debug, profile, release, jit_release) |
| String get cliName => snakeCase(name); |
| |
| /// [cliName] formatted in sentence case. |
| /// |
| /// (e.g. Debug, Profile, Release, Jit_release) |
| String get uppercaseName => sentenceCase(cliName); |
| |
| /// [cliName] with `_` replaced with a space. |
| /// |
| /// (e.g. debug, profile, release, jit release) |
| String get friendlyName => cliName.replaceAll('_', ' '); |
| |
| /// [friendlyName] formatted in sentence case. |
| /// |
| /// (e.g. Debug, Profile, Release, Jit release) |
| String get uppercaseFriendlyName => sentenceCase(friendlyName); |
| |
| @override |
| String toString() => cliName; |
| } |
| |
| /// Environment type of the target device. |
| enum EnvironmentType { physical, simulator } |
| |
| String? validatedBuildNumberForPlatform( |
| TargetPlatform targetPlatform, |
| String? buildNumber, |
| Logger logger, |
| ) { |
| if (buildNumber == null) { |
| return null; |
| } |
| if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { |
| // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html |
| final disallowed = RegExp(r'[^\d\.]'); |
| String tmpBuildNumber = buildNumber.replaceAll(disallowed, ''); |
| if (tmpBuildNumber.isEmpty) { |
| return null; |
| } |
| final List<String> segments = tmpBuildNumber |
| .split('.') |
| .where((String segment) => segment.isNotEmpty) |
| .toList(); |
| if (segments.isEmpty) { |
| segments.add('0'); |
| } |
| tmpBuildNumber = segments.join('.'); |
| if (tmpBuildNumber != buildNumber) { |
| logger.printTrace( |
| 'Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n' |
| 'See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html', |
| ); |
| } |
| return tmpBuildNumber; |
| } |
| if (targetPlatform == TargetPlatform.android_arm || |
| targetPlatform == TargetPlatform.android_arm64 || |
| targetPlatform == TargetPlatform.android_x64) { |
| // See versionCode at https://developer.android.com/studio/publish/versioning |
| final disallowed = RegExp(r'[^\d]'); |
| String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, ''); |
| int tmpBuildNumberInt = int.tryParse(tmpBuildNumberStr) ?? 0; |
| if (tmpBuildNumberInt < 1) { |
| tmpBuildNumberInt = 1; |
| } |
| tmpBuildNumberStr = tmpBuildNumberInt.toString(); |
| if (tmpBuildNumberStr != buildNumber) { |
| logger.printTrace( |
| 'Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n' |
| 'See versionCode at https://developer.android.com/studio/publish/versioning', |
| ); |
| } |
| return tmpBuildNumberStr; |
| } |
| return buildNumber; |
| } |
| |
| String? validatedBuildNameForPlatform( |
| TargetPlatform targetPlatform, |
| String? buildName, |
| Logger logger, |
| ) { |
| if (buildName == null) { |
| return null; |
| } |
| if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { |
| // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html |
| final disallowed = RegExp(r'[^\d\.]'); |
| String tmpBuildName = buildName.replaceAll(disallowed, ''); |
| if (tmpBuildName.isEmpty) { |
| return null; |
| } |
| final List<String> segments = tmpBuildName |
| .split('.') |
| .where((String segment) => segment.isNotEmpty) |
| .toList(); |
| while (segments.length < 3) { |
| segments.add('0'); |
| } |
| tmpBuildName = segments.join('.'); |
| if (tmpBuildName != buildName) { |
| logger.printTrace( |
| 'Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n' |
| 'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html', |
| ); |
| } |
| return tmpBuildName; |
| } |
| if (targetPlatform == TargetPlatform.android || |
| targetPlatform == TargetPlatform.android_arm || |
| targetPlatform == TargetPlatform.android_arm64 || |
| targetPlatform == TargetPlatform.android_x64) { |
| // See versionName at https://developer.android.com/studio/publish/versioning |
| return buildName; |
| } |
| return buildName; |
| } |
| |
| // Returns true if the selected build mode uses ahead-of-time compilation. |
| bool isAotBuildMode(BuildMode mode) { |
| return mode == BuildMode.profile || mode == BuildMode.release; |
| } |
| |
| // Returns true if the given build mode can be used on emulators / simulators. |
| bool isEmulatorBuildMode(BuildMode mode) { |
| return mode == BuildMode.debug; |
| } |
| |
| enum TargetPlatform { |
| android, |
| ios, |
| darwin, |
| linux_x64, |
| linux_arm64, |
| windows_x64, |
| windows_arm64, |
| fuchsia_arm64, |
| fuchsia_x64, |
| tester, |
| web_javascript, |
| // The arch specific android target platforms are soft-deprecated. |
| // Instead of using TargetPlatform as a combination arch + platform |
| // the code will be updated to carry arch information in [DarwinArch] |
| // and [AndroidArch]. |
| android_arm, |
| android_arm64, |
| android_x64, |
| unsupported; |
| |
| String get fuchsiaArchForTargetPlatform { |
| switch (this) { |
| case TargetPlatform.fuchsia_arm64: |
| return 'arm64'; |
| case TargetPlatform.fuchsia_x64: |
| return 'x64'; |
| case TargetPlatform.android: |
| case TargetPlatform.android_arm: |
| case TargetPlatform.android_arm64: |
| case TargetPlatform.android_x64: |
| case TargetPlatform.darwin: |
| case TargetPlatform.ios: |
| case TargetPlatform.linux_arm64: |
| case TargetPlatform.linux_x64: |
| case TargetPlatform.tester: |
| case TargetPlatform.web_javascript: |
| case TargetPlatform.windows_x64: |
| case TargetPlatform.windows_arm64: |
| case TargetPlatform.unsupported: |
| throw UnsupportedError('Unexpected Fuchsia platform $this'); |
| } |
| } |
| |
| String get simpleName { |
| switch (this) { |
| case TargetPlatform.linux_x64: |
| case TargetPlatform.darwin: |
| case TargetPlatform.windows_x64: |
| return 'x64'; |
| case TargetPlatform.linux_arm64: |
| case TargetPlatform.windows_arm64: |
| return 'arm64'; |
| case TargetPlatform.android: |
| case TargetPlatform.android_arm: |
| case TargetPlatform.android_arm64: |
| case TargetPlatform.android_x64: |
| case TargetPlatform.fuchsia_arm64: |
| case TargetPlatform.fuchsia_x64: |
| case TargetPlatform.ios: |
| case TargetPlatform.tester: |
| case TargetPlatform.web_javascript: |
| case TargetPlatform.unsupported: |
| throw UnsupportedError('Unexpected target platform $this'); |
| } |
| } |
| |
| static Never throwUnsupportedTarget() => |
| throw UnsupportedError('Target platform is unsupported.'); |
| } |
| |
| /// iOS and macOS target device architecture. |
| // |
| // TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64. |
| enum DarwinArch { |
| armv7, // Deprecated. Used to display 32-bit unsupported devices. |
| arm64, |
| x86_64; |
| |
| /// Returns the Dart SDK's name for the specified target architecture. |
| /// |
| /// When building for Darwin platforms, the tool invokes architecture-specific |
| /// variants of `gen_snapshot`, one for each target architecture. The output |
| /// instructions are then built into architecture-specific binaries, which are |
| /// merged into a universal binary using the `lipo` tool. |
| String get dartName { |
| return switch (this) { |
| DarwinArch.armv7 => 'armv7', |
| DarwinArch.arm64 => 'arm64', |
| DarwinArch.x86_64 => 'x64', |
| }; |
| } |
| } |
| |
| // TODO(zanderso): replace all android TargetPlatform usage with AndroidArch. |
| enum AndroidArch { |
| armeabi_v7a, |
| arm64_v8a, |
| x86_64; |
| |
| String get archName => switch (this) { |
| AndroidArch.armeabi_v7a => 'armeabi-v7a', |
| AndroidArch.arm64_v8a => 'arm64-v8a', |
| AndroidArch.x86_64 => 'x86_64', |
| }; |
| |
| String get platformName => switch (this) { |
| AndroidArch.armeabi_v7a => 'android-arm', |
| AndroidArch.arm64_v8a => 'android-arm64', |
| AndroidArch.x86_64 => 'android-x64', |
| }; |
| } |
| |
| /// The default set of iOS device architectures to build for. |
| List<DarwinArch> defaultIOSArchsForEnvironment( |
| EnvironmentType environmentType, |
| Artifacts artifacts, |
| ) { |
| // Handle single-arch local engines. |
| final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo; |
| if (localEngineInfo != null) { |
| final String localEngineName = localEngineInfo.localTargetName; |
| if (localEngineName.contains('_arm64')) { |
| return <DarwinArch>[DarwinArch.arm64]; |
| } |
| if (localEngineName.contains('_sim')) { |
| return <DarwinArch>[DarwinArch.x86_64]; |
| } |
| } else if (environmentType == EnvironmentType.simulator) { |
| return <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64]; |
| } |
| return <DarwinArch>[DarwinArch.arm64]; |
| } |
| |
| /// The default set of macOS device architectures to build for. |
| List<DarwinArch> defaultMacOSArchsForEnvironment(Artifacts artifacts) { |
| // Handle single-arch local engines. |
| final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo; |
| if (localEngineInfo != null) { |
| if (localEngineInfo.localTargetName.contains('_arm64')) { |
| return <DarwinArch>[DarwinArch.arm64]; |
| } |
| return <DarwinArch>[DarwinArch.x86_64]; |
| } |
| return <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64]; |
| } |
| |
| DarwinArch getIOSArchForName(String arch) { |
| switch (arch) { |
| case 'armv7': |
| case 'armv7f': // iPhone 4S. |
| case 'armv7s': // iPad 4. |
| return DarwinArch.armv7; |
| case 'arm64': |
| case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices. |
| return DarwinArch.arm64; |
| case 'x86_64': |
| return DarwinArch.x86_64; |
| } |
| throw Exception('Unsupported iOS arch name "$arch"'); |
| } |
| |
| DarwinArch getDarwinArchForName(String arch) { |
| return switch (arch) { |
| 'arm64' => DarwinArch.arm64, |
| 'x86_64' => DarwinArch.x86_64, |
| _ => throw Exception('Unsupported MacOS arch name "$arch"'), |
| }; |
| } |
| |
| String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) { |
| return switch (platform) { |
| TargetPlatform.ios when darwinArch != null => 'ios-${darwinArch.name}', |
| TargetPlatform.darwin when darwinArch != null => 'darwin-${darwinArch.name}', |
| TargetPlatform.ios => 'ios', |
| TargetPlatform.darwin => 'darwin', |
| TargetPlatform.android_arm => 'android-arm', |
| TargetPlatform.android_arm64 => 'android-arm64', |
| TargetPlatform.android_x64 => 'android-x64', |
| TargetPlatform.linux_x64 => 'linux-x64', |
| TargetPlatform.linux_arm64 => 'linux-arm64', |
| TargetPlatform.windows_x64 => 'windows-x64', |
| TargetPlatform.windows_arm64 => 'windows-arm64', |
| TargetPlatform.fuchsia_arm64 => 'fuchsia-arm64', |
| TargetPlatform.fuchsia_x64 => 'fuchsia-x64', |
| TargetPlatform.tester => 'flutter-tester', |
| TargetPlatform.web_javascript => 'web-javascript', |
| TargetPlatform.android => 'android', |
| TargetPlatform.unsupported => 'unsupported', |
| }; |
| } |
| |
| TargetPlatform getTargetPlatformForName(String platform) { |
| return switch (platform) { |
| 'android' => TargetPlatform.android, |
| 'android-arm' => TargetPlatform.android_arm, |
| 'android-arm64' => TargetPlatform.android_arm64, |
| 'android-x64' => TargetPlatform.android_x64, |
| 'fuchsia-arm64' => TargetPlatform.fuchsia_arm64, |
| 'fuchsia-x64' => TargetPlatform.fuchsia_x64, |
| 'ios' => TargetPlatform.ios, |
| // For backward-compatibility and also for Tester, where it must match |
| // host platform name (HostPlatform.darwin_x64) |
| 'darwin' || 'darwin-x64' || 'darwin-arm64' => TargetPlatform.darwin, |
| 'linux-x64' => TargetPlatform.linux_x64, |
| 'linux-arm64' => TargetPlatform.linux_arm64, |
| 'windows-x64' => TargetPlatform.windows_x64, |
| 'windows-arm64' => TargetPlatform.windows_arm64, |
| 'web-javascript' => TargetPlatform.web_javascript, |
| 'flutter-tester' => TargetPlatform.tester, |
| _ => throw Exception('Unsupported platform name "$platform"'), |
| }; |
| } |
| |
| AndroidArch getAndroidArchForName(String platform) { |
| return switch (platform) { |
| 'android-arm' => AndroidArch.armeabi_v7a, |
| 'android-arm64' => AndroidArch.arm64_v8a, |
| 'android-x64' => AndroidArch.x86_64, |
| _ => throw Exception('Unsupported Android arch name "$platform"'), |
| }; |
| } |
| |
| DarwinArch getCurrentDarwinArch() { |
| return switch (globals.os.hostPlatform) { |
| HostPlatform.darwin_arm64 => DarwinArch.arm64, |
| HostPlatform.darwin_x64 => DarwinArch.x86_64, |
| final HostPlatform unsupported => throw Exception( |
| 'Unsupported Darwin host platform "$unsupported"', |
| ), |
| }; |
| } |
| |
| HostPlatform getCurrentHostPlatform() { |
| if (globals.platform.isMacOS) { |
| return switch (getCurrentDarwinArch()) { |
| DarwinArch.arm64 => HostPlatform.darwin_arm64, |
| DarwinArch.x86_64 => HostPlatform.darwin_x64, |
| DarwinArch.armv7 => throw Exception('Unsupported macOS arch "amv7"'), |
| }; |
| } |
| if (globals.platform.isLinux) { |
| // support x64 and arm64 architecture. |
| return globals.os.hostPlatform; |
| } |
| if (globals.platform.isWindows) { |
| return HostPlatform.windows_x64; |
| } |
| |
| globals.printWarning('Unsupported host platform, defaulting to Linux'); |
| |
| return HostPlatform.linux_x64; |
| } |
| |
| /// Returns the top-level build output directory. |
| String getBuildDirectory([Config? config, FileSystem? fileSystem]) { |
| // TODO(andrewkolos): Prefer required parameters instead of falling back to globals. |
| // TODO(johnmccutchan): Stop calling this function as part of setting |
| // up command line argument processing. |
| final Config localConfig = config ?? globals.config; |
| final FileSystem localFilesystem = fileSystem ?? globals.fs; |
| |
| final String buildDir = localConfig.getValue('build-dir') as String? ?? 'build'; |
| if (localFilesystem.path.isAbsolute(buildDir)) { |
| throw Exception('build-dir config setting in ${globals.config.configPath} must be relative'); |
| } |
| return buildDir; |
| } |
| |
| /// Returns the Android build output directory. |
| String getAndroidBuildDirectory() { |
| // TODO(cbracken): move to android subdir. |
| return getBuildDirectory(); |
| } |
| |
| /// Returns the AOT build output directory. |
| String getAotBuildDirectory() { |
| return globals.fs.path.join(getBuildDirectory(), 'aot'); |
| } |
| |
| /// Returns the asset build output directory. |
| String getAssetBuildDirectory([Config? config, FileSystem? fileSystem]) { |
| return (fileSystem ?? globals.fs).path.join( |
| getBuildDirectory(config, fileSystem), |
| 'flutter_assets', |
| ); |
| } |
| |
| /// Returns the iOS build output directory. |
| String getIosBuildDirectory() { |
| return globals.fs.path.join(getBuildDirectory(), FlutterDarwinPlatform.ios.name); |
| } |
| |
| /// Returns the macOS build output directory. |
| String getMacOSBuildDirectory() { |
| return globals.fs.path.join(getBuildDirectory(), FlutterDarwinPlatform.macos.name); |
| } |
| |
| /// Returns the web build output directory. |
| String getWebBuildDirectory() { |
| return globals.fs.path.join(getBuildDirectory(), 'web'); |
| } |
| |
| /// Returns the Linux build output directory. |
| String getLinuxBuildDirectory([TargetPlatform? targetPlatform]) { |
| final String arch = (targetPlatform == null) |
| ? _getCurrentHostPlatformArchName() |
| : targetPlatform.simpleName; |
| final subDirs = 'linux/$arch'; |
| return globals.fs.path.join(getBuildDirectory(), subDirs); |
| } |
| |
| /// Returns the Windows build output directory. |
| String getWindowsBuildDirectory(TargetPlatform targetPlatform) { |
| final String arch = targetPlatform.simpleName; |
| return globals.fs.path.join(getBuildDirectory(), 'windows', arch); |
| } |
| |
| /// Returns the Fuchsia build output directory. |
| String getFuchsiaBuildDirectory() { |
| return globals.fs.path.join(getBuildDirectory(), 'fuchsia'); |
| } |
| |
| /// Defines specified via the `--dart-define` command-line option. |
| /// |
| /// These values are URI-encoded and then combined into a comma-separated string. |
| const kDartDefines = 'DartDefines'; |
| |
| /// The define to pass a [BuildMode]. |
| const kBuildMode = 'BuildMode'; |
| |
| /// The define to pass whether we compile 64-bit android-arm code. |
| const kTargetPlatform = 'TargetPlatform'; |
| |
| /// The define to control what target file is used. |
| const kTargetFile = 'TargetFile'; |
| |
| /// Whether to enable or disable track widget creation. |
| const kTrackWidgetCreation = 'TrackWidgetCreation'; |
| |
| /// If provided, the frontend server will be started in JIT mode from this file. |
| const kFrontendServerStarterPath = 'FrontendServerStarterPath'; |
| |
| /// Additional configuration passed to the dart front end. |
| /// |
| /// This is expected to be a comma separated list of strings. |
| const kExtraFrontEndOptions = 'ExtraFrontEndOptions'; |
| |
| /// Additional configuration passed to gen_snapshot. |
| /// |
| /// This is expected to be a comma separated list of strings. |
| const kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions'; |
| |
| /// Whether the build should run gen_snapshot as a split aot build for deferred |
| /// components. |
| const kDeferredComponents = 'DeferredComponents'; |
| |
| /// Whether to strip source code information out of release builds and where to save it. |
| const kSplitDebugInfo = 'SplitDebugInfo'; |
| |
| /// Alternative scheme for file URIs. |
| /// |
| /// May be used along with [kFileSystemRoots] to support a multi-root |
| /// filesystem. |
| const kFileSystemScheme = 'FileSystemScheme'; |
| |
| /// Additional filesystem roots. |
| /// |
| /// If provided, must be used along with [kFileSystemScheme]. |
| const kFileSystemRoots = 'FileSystemRoots'; |
| |
| /// The define to control what iOS architectures are built for. |
| /// |
| /// This is expected to be a space-delimited list of architectures. If not |
| /// provided, defaults to arm64. |
| const kIosArchs = 'IosArchs'; |
| |
| /// The define to control what macOS architectures are built for. |
| /// |
| /// This is expected to be a space-delimited list of architectures. If not |
| /// provided, defaults to x86_64 and arm64. |
| /// |
| /// Supported values are x86_64 and arm64. |
| const kDarwinArchs = 'DarwinArchs'; |
| |
| /// The define to control what Android architectures are built for. |
| /// |
| /// This is expected to be a space-delimited list of architectures. |
| const kAndroidArchs = 'AndroidArchs'; |
| |
| /// The define to control what min Android SDK version is built for. |
| /// |
| /// This is expected to be int. |
| /// |
| /// If not provided, defaults to `minSdkVersion` from gradle_utils.dart. |
| /// |
| /// This is passed in by the Flutter Gradle plugin's invocation of `flutter assemble`. |
| /// |
| /// For more info, see: |
| /// https://developer.android.com/ndk/guides/sdk-versions#minsdkversion |
| /// https://developer.android.com/ndk/guides/other_build_systems#overview |
| const kMinSdkVersion = 'MinSdkVersion'; |
| |
| /// Path to the SDK root to be used as the isysroot. |
| const kSdkRoot = 'SdkRoot'; |
| |
| /// Whether to enable Dart obfuscation and where to save the symbol map. |
| const kDartObfuscation = 'DartObfuscation'; |
| |
| /// Whether to enable Native Assets. |
| /// |
| /// If true, native assets are built and the mapping for native assets lookup |
| /// at runtime is embedded in the kernel file. |
| /// |
| /// If false, native assets are not built, and an empty mapping is embedded in |
| /// the kernel file. Used for targets that trigger kernel builds but |
| /// are not OS/architecture specific. |
| /// |
| /// Supported values are 'true' and 'false'. |
| /// |
| /// Defaults to 'true'. |
| const kNativeAssets = 'NativeAssets'; |
| |
| /// An output directory where one or more code-size measurements may be written. |
| const kCodeSizeDirectory = 'CodeSizeDirectory'; |
| |
| /// SHA identifier of the Apple developer code signing identity. |
| /// |
| /// Same as EXPANDED_CODE_SIGN_IDENTITY Xcode build setting. |
| /// Also discoverable via `security find-identity -p codesigning`. |
| const kCodesignIdentity = 'CodesignIdentity'; |
| |
| /// The build define controlling whether icon fonts should be stripped down to |
| /// only the glyphs used by the application. |
| const kIconTreeShakerFlag = 'TreeShakeIcons'; |
| |
| /// Controls whether a web build should use local canvaskit or the CDN |
| const kUseLocalCanvasKitFlag = 'UseLocalCanvasKit'; |
| |
| /// The define to pass build name |
| const kBuildName = 'BuildName'; |
| |
| /// The app flavor to build. |
| const kFlavor = 'Flavor'; |
| |
| /// Environment variable of the flavor to be set in dartDefines to be accessed |
| /// by the `appFlavor` service. |
| const kAppFlavor = 'FLUTTER_APP_FLAVOR'; |
| |
| /// Environment variable of the enabled feature flags to be set in the |
| /// dartDefines. |
| /// |
| /// This tells the framework which features are on. |
| const kEnabledFeatureFlags = 'FLUTTER_ENABLED_FEATURE_FLAGS'; |
| |
| /// The Xcode configuration used to build the project. |
| const kXcodeConfiguration = 'Configuration'; |
| |
| /// The Xcode build setting SRCROOT. Identifies the directory containing the |
| /// Xcode target's source files. |
| const kSrcRoot = 'SrcRoot'; |
| |
| /// The Xcode build setting TARGET_DEVICE_OS_VERSION. The iOS version of the |
| /// target device. Only available if a specific device is being targeted during |
| /// the build. |
| const kTargetDeviceOSVersion = 'TargetDeviceOSVersion'; |
| |
| /// The define to pass build number |
| const kBuildNumber = 'BuildNumber'; |
| |
| /// The action Xcode is taking. |
| /// |
| /// Will be "build" when building and "install" when archiving. |
| const kXcodeAction = 'Action'; |
| |
| /// The define of the Xcode build Pre-action. |
| /// |
| /// Will be "PrepareFramework" when copying the Flutter/FlutterMacOS framework |
| /// to the BUILT_PRODUCTS_DIR prior to the build. |
| const kXcodePreAction = 'PreBuildAction'; |
| |
| final Converter<String, String> _defineEncoder = utf8.encoder.fuse(base64.encoder); |
| final Converter<String, String> _defineDecoder = base64.decoder.fuse(utf8.decoder); |
| |
| /// Encode a List of dart defines in a base64 string. |
| /// |
| /// This encoding does not include `,`, which is used to distinguish |
| /// the individual entries, nor does it include `%` which is often a |
| /// control character on windows command lines. |
| /// |
| /// When decoding this string, it can be safely split on commas, since any |
| /// user provided commands will still be encoded. |
| /// |
| /// If the presence of the `/` character ends up being an issue, this can |
| /// be changed to use base32 instead. |
| String encodeDartDefines(List<String> defines) { |
| return defines.map(_defineEncoder.convert).join(','); |
| } |
| |
| List<String> decodeCommaSeparated(Map<String, String> environmentDefines, String key) { |
| if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) { |
| return <String>[]; |
| } |
| return environmentDefines[key]!.split(',').cast<String>().toList(); |
| } |
| |
| /// Dart defines are encoded inside [environmentDefines] as a comma-separated list. |
| List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) { |
| if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) { |
| return <String>[]; |
| } |
| return environmentDefines[key]! |
| .split(',') |
| .map<Object>(_defineDecoder.convert) |
| .cast<String>() |
| .toList(); |
| } |
| |
| /// Indicates the module system DDC is targeting. |
| enum DdcModuleFormat { amd, ddc } |
| |
| // TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060. |
| ({DdcModuleFormat? ddcModuleFormat, bool? canaryFeatures}) |
| _ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs(List<String>? extraFrontEndArgs) { |
| DdcModuleFormat? ddcModuleFormat; |
| bool? canaryFeatures; |
| if (extraFrontEndArgs != null) { |
| const ddcModuleFormatArg = '--dartdevc-module-format='; |
| const canaryFeaturesArg = '--dartdevc-canary'; |
| for (final String flag in extraFrontEndArgs) { |
| if (flag.startsWith(ddcModuleFormatArg)) { |
| final String moduleFormatString = flag.substring(ddcModuleFormatArg.length, flag.length); |
| assert(ddcModuleFormat == null); |
| ddcModuleFormat = DdcModuleFormat.values.byName(moduleFormatString); |
| } else if (flag == canaryFeaturesArg) { |
| canaryFeatures = true; |
| } |
| } |
| } |
| return (ddcModuleFormat: ddcModuleFormat, canaryFeatures: canaryFeatures); |
| } |
| |
| String _getCurrentHostPlatformArchName() { |
| final HostPlatform hostPlatform = getCurrentHostPlatform(); |
| return hostPlatform.platformName; |
| } |
| |
| String? _uncapitalize(String? s) { |
| if (s == null || s.isEmpty) { |
| return s; |
| } |
| return s.substring(0, 1).toLowerCase() + s.substring(1); |
| } |