// 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:package_config/package_config_types.dart'; import 'base/config.dart'; import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/os.dart'; import 'base/utils.dart'; import 'convert.dart'; import 'globals_null_migrated.dart' as globals; /// Whether icon font subsetting is enabled by default. const bool kIconTreeShakerEnabledDefault = true; /// Information about a build to be performed or used. class BuildInfo { const BuildInfo( this.mode, this.flavor, { this.trackWidgetCreation = false, List<String>? extraFrontEndOptions, List<String>? extraGenSnapshotOptions, List<String>? fileSystemRoots, this.androidProjectArgs = const <String>[], this.fileSystemScheme, this.buildNumber, this.buildName, this.splitDebugInfoPath, this.dartObfuscation = false, List<String>? dartDefines, this.bundleSkSLPath, List<String>? dartExperiments, required this.treeShakeIcons, this.performanceMeasurementFile, this.packagesPath = '.packages', // TODO(zanderso): make this required and remove the default. this.nullSafetyMode = NullSafetyMode.sound, this.codeSizeDirectory, this.androidGradleDaemon = true, this.packageConfig = PackageConfig.empty, this.initializeFromDill, }) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[], extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[], fileSystemRoots = fileSystemRoots ?? const <String>[], dartDefines = dartDefines ?? const <String>[], dartExperiments = dartExperiments ?? const <String>[]; final BuildMode mode; /// The null safety mode the application should be run in. /// /// If not provided, defaults to [NullSafetyMode.autodetect]. final NullSafetyMode nullSafetyMode; /// 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 .packages 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 `.packages`. final String packagesPath; final List<String> fileSystemRoots; final String? fileSystemScheme; /// Whether the build should track widget creation locations. final bool trackWidgetCreation; /// 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. 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. 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; /// An optional path to a JSON containing object SkSL shaders. /// /// Currently this is only supported for Android builds. final String? bundleSkSLPath; /// 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; /// Additional key value pairs that are passed directly to the gradle project via the `-P` /// flag. final List<String> androidProjectArgs; /// 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; static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false); static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault); static const BuildInfo release = BuildInfo(BuildMode.release, null, treeShakeIcons: kIconTreeShakerEnabledDefault); /// 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 => getModeName(mode); String get friendlyModeName => getFriendlyModeName(mode); /// the flavor name in the output files is lower-cased (see flutter.gradle), /// so the lower cased flavor name is used to compute the output file name String? get lowerCasedFlavor => flavor?.toLowerCase(); /// 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: getNameForBuildMode(mode), if (dartDefines.isNotEmpty) kDartDefines: encodeDartDefines(dartDefines), if (dartObfuscation != null) kDartObfuscation: dartObfuscation.toString(), if (extraFrontEndOptions.isNotEmpty) kExtraFrontEndOptions: extraFrontEndOptions.join(','), if (extraGenSnapshotOptions.isNotEmpty) kExtraGenSnapshotOptions: extraGenSnapshotOptions.join(','), if (splitDebugInfoPath != null) kSplitDebugInfo: splitDebugInfoPath!, if (trackWidgetCreation != null) kTrackWidgetCreation: trackWidgetCreation.toString(), if (treeShakeIcons != null) kIconTreeShakerFlag: treeShakeIcons.toString(), if (bundleSkSLPath != null) kBundleSkSLPath: bundleSkSLPath!, if (codeSizeDirectory != null) kCodeSizeDirectory: codeSizeDirectory!, if (fileSystemRoots.isNotEmpty) kFileSystemRoots: fileSystemRoots.join(','), if (fileSystemScheme != null) kFileSystemScheme: fileSystemScheme!, }; } /// 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), if (dartObfuscation != null) 'DART_OBFUSCATION': dartObfuscation.toString(), if (extraFrontEndOptions.isNotEmpty) 'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','), if (extraGenSnapshotOptions.isNotEmpty) 'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','), if (splitDebugInfoPath != null) 'SPLIT_DEBUG_INFO': splitDebugInfoPath!, if (trackWidgetCreation != null) 'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(), if (treeShakeIcons != null) 'TREE_SHAKE_ICONS': treeShakeIcons.toString(), if (performanceMeasurementFile != null) 'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile!, if (bundleSkSLPath != null) 'BUNDLE_SKSL_PATH': bundleSkSLPath!, if (packagesPath != null) 'PACKAGE_CONFIG': packagesPath, if (codeSizeDirectory != null) 'CODE_SIZE_DIRECTORY': codeSizeDirectory!, }; } /// 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)}', if (dartObfuscation != null) '-Pdart-obfuscation=$dartObfuscation', 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', if (trackWidgetCreation != null) '-Ptrack-widget-creation=$trackWidgetCreation', if (treeShakeIcons != null) '-Ptree-shake-icons=$treeShakeIcons', if (performanceMeasurementFile != null) '-Pperformance-measurement-file=$performanceMeasurementFile', if (bundleSkSLPath != null) '-Pbundle-sksl-path=$bundleSkSLPath', if (codeSizeDirectory != null) '-Pcode-size-directory=$codeSizeDirectory', for (String projectArg in androidProjectArgs) '-P$projectArg', ]; } } /// 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. class BuildMode { const BuildMode._(this.name); factory BuildMode.fromName(String value) { switch (value) { case 'debug': return BuildMode.debug; case 'profile': return BuildMode.profile; case 'release': return BuildMode.release; case 'jit_release': return BuildMode.jitRelease; } throw ArgumentError('$value is not a supported build mode'); } /// Built in JIT mode with no optimizations, enabled asserts, and an observatory. static const BuildMode debug = BuildMode._('debug'); /// Built in AOT mode with some optimizations and an observatory. static const BuildMode profile = BuildMode._('profile'); /// Built in AOT mode with all optimizations and no observatory. static const BuildMode release = BuildMode._('release'); /// Built in JIT mode with all optimizations and no observatory. static const BuildMode jitRelease = BuildMode._('jit_release'); static const List<BuildMode> values = <BuildMode>[ debug, profile, release, jitRelease, ]; static const Set<BuildMode> releaseModes = <BuildMode>{ release, jitRelease, }; static const Set<BuildMode> 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; /// The name for this build mode. final String name; @override String toString() => name; } /// Return the name for the build mode, or "any" if null. String getNameForBuildMode(BuildMode buildMode) { return buildMode.name; } /// Returns the [BuildMode] for a particular `name`. BuildMode getBuildModeForName(String name) { return BuildMode.fromName(name); } /// 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 RegExp 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 || targetPlatform == TargetPlatform.android_x86) { // See versionCode at https://developer.android.com/studio/publish/versioning final RegExp 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 RegExp 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 || targetPlatform == TargetPlatform.android_x86) { // See versionName at https://developer.android.com/studio/publish/versioning return buildName; } return buildName; } String getModeName(BuildMode mode) => getEnumName(mode); String getFriendlyModeName(BuildMode mode) { return snakeCase(getModeName(mode)).replaceAll('_', ' '); } // 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_uwp_x64, 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, android_x86, } /// iOS and macOS target device architecture. // // TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64. enum DarwinArch { armv7, arm64, x86_64, } // TODO(zanderso): replace all android TargetPlatform usage with AndroidArch. enum AndroidArch { armeabi_v7a, arm64_v8a, x86, x86_64, } /// The default set of iOS device architectures to build for. List<DarwinArch> defaultIOSArchsForEnvironment( EnvironmentType environmentType) { if (environmentType == EnvironmentType.simulator) { return <DarwinArch>[ DarwinArch.x86_64, DarwinArch.arm64, ]; } return <DarwinArch>[ DarwinArch.armv7, DarwinArch.arm64, ]; } String getNameForDarwinArch(DarwinArch arch) { switch (arch) { case DarwinArch.armv7: return 'armv7'; case DarwinArch.arm64: return 'arm64'; case DarwinArch.x86_64: return 'x86_64'; } } 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) { switch (arch) { case 'arm64': return DarwinArch.arm64; case 'x86_64': return DarwinArch.x86_64; } throw Exception('Unsupported MacOS arch name "$arch"'); } String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) { switch (platform) { case TargetPlatform.android_arm: return 'android-arm'; case TargetPlatform.android_arm64: return 'android-arm64'; case TargetPlatform.android_x64: return 'android-x64'; case TargetPlatform.android_x86: return 'android-x86'; case TargetPlatform.ios: if (darwinArch != null) { return 'ios-${getNameForDarwinArch(darwinArch)}'; } return 'ios'; case TargetPlatform.darwin: if (darwinArch != null) { return 'darwin-${getNameForDarwinArch(darwinArch)}'; } return 'darwin'; case TargetPlatform.linux_x64: return 'linux-x64'; case TargetPlatform.linux_arm64: return 'linux-arm64'; case TargetPlatform.windows_x64: return 'windows-x64'; case TargetPlatform.windows_uwp_x64: return 'windows-uwp-x64'; case TargetPlatform.fuchsia_arm64: return 'fuchsia-arm64'; case TargetPlatform.fuchsia_x64: return 'fuchsia-x64'; case TargetPlatform.tester: return 'flutter-tester'; case TargetPlatform.web_javascript: return 'web-javascript'; case TargetPlatform.android: return 'android'; } } TargetPlatform getTargetPlatformForName(String platform) { switch (platform) { case 'android': return TargetPlatform.android; case 'android-arm': return TargetPlatform.android_arm; case 'android-arm64': return TargetPlatform.android_arm64; case 'android-x64': return TargetPlatform.android_x64; case 'android-x86': return TargetPlatform.android_x86; case 'fuchsia-arm64': return TargetPlatform.fuchsia_arm64; case 'fuchsia-x64': return TargetPlatform.fuchsia_x64; case 'ios': return TargetPlatform.ios; case 'darwin': // For backward-compatibility and also for Tester, where it must match // host platform name (HostPlatform.darwin_x64) case 'darwin-x64': case 'darwin-arm': return TargetPlatform.darwin; case 'linux-x64': return TargetPlatform.linux_x64; case 'linux-arm64': return TargetPlatform.linux_arm64; case 'windows-x64': return TargetPlatform.windows_x64; case 'windows-uwp-x64': return TargetPlatform.windows_uwp_x64; case 'web-javascript': return TargetPlatform.web_javascript; } throw Exception('Unsupported platform name "$platform"'); } AndroidArch getAndroidArchForName(String platform) { switch (platform) { case 'android-arm': return AndroidArch.armeabi_v7a; case 'android-arm64': return AndroidArch.arm64_v8a; case 'android-x64': return AndroidArch.x86_64; case 'android-x86': return AndroidArch.x86; } throw Exception('Unsupported Android arch name "$platform"'); } String getNameForAndroidArch(AndroidArch arch) { switch (arch) { case AndroidArch.armeabi_v7a: return 'armeabi-v7a'; case AndroidArch.arm64_v8a: return 'arm64-v8a'; case AndroidArch.x86_64: return 'x86_64'; case AndroidArch.x86: return 'x86'; } } String getPlatformNameForAndroidArch(AndroidArch arch) { switch (arch) { case AndroidArch.armeabi_v7a: return 'android-arm'; case AndroidArch.arm64_v8a: return 'android-arm64'; case AndroidArch.x86_64: return 'android-x64'; case AndroidArch.x86: return 'android-x86'; } } String fuchsiaArchForTargetPlatform(TargetPlatform targetPlatform) { switch (targetPlatform) { case TargetPlatform.fuchsia_arm64: return 'arm64'; case TargetPlatform.fuchsia_x64: return 'x64'; default: throw UnsupportedError('Unexpected Fuchsia platform $targetPlatform'); } } HostPlatform getCurrentHostPlatform() { if (globals.platform.isMacOS) { return HostPlatform.darwin_x64; } if (globals.platform.isLinux) { // support x64 and arm64 architecture. return globals.os.hostPlatform; } if (globals.platform.isWindows) { return HostPlatform.windows_x64; } globals.printError('Unsupported host platform, defaulting to Linux'); return HostPlatform.linux_x64; } /// Returns the top-level build output directory. String getBuildDirectory([Config? config, FileSystem? fileSystem]) { // TODO(johnmccutchan): Stop calling this function as part of setting // up command line argument processing. if (context == null) { return 'build'; } final Config localConfig = config ?? globals.config; final FileSystem localFilesystem = fileSystem ?? globals.fs; if (localConfig == null) { return 'build'; } 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() { return globals.fs.path.join(getBuildDirectory(), 'flutter_assets'); } /// Returns the iOS build output directory. String getIosBuildDirectory() { return globals.fs.path.join(getBuildDirectory(), 'ios'); } /// Returns the macOS build output directory. String getMacOSBuildDirectory() { return globals.fs.path.join(getBuildDirectory(), 'macos'); } /// 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() : getNameForTargetPlatformArch(targetPlatform); final String subDirs = 'linux/$arch'; return globals.fs.path.join(getBuildDirectory(), subDirs); } /// Returns the Windows build output directory. String getWindowsBuildDirectory() { return globals.fs.path.join(getBuildDirectory(), 'windows'); } /// Returns the Windows UWP build output directory. String getWindowsBuildUwpDirectory() { return globals.fs.path.join(getBuildDirectory(), 'winuwp'); } /// 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 String kDartDefines = 'DartDefines'; /// The define to pass a [BuildMode]. const String kBuildMode = 'BuildMode'; /// The define to pass whether we compile 64-bit android-arm code. const String kTargetPlatform = 'TargetPlatform'; /// The define to control what target file is used. const String kTargetFile = 'TargetFile'; /// The define to control whether the AOT snapshot is built with bitcode. const String kBitcodeFlag = 'EnableBitcode'; /// Whether to enable or disable track widget creation. const String kTrackWidgetCreation = 'TrackWidgetCreation'; /// Additional configuration passed to the dart front end. /// /// This is expected to be a comma separated list of strings. const String kExtraFrontEndOptions = 'ExtraFrontEndOptions'; /// Additional configuration passed to gen_snapshot. /// /// This is expected to be a comma separated list of strings. const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions'; /// Whether the build should run gen_snapshot as a split aot build for deferred /// components. const String kDeferredComponents = 'DeferredComponents'; /// Whether to strip source code information out of release builds and where to save it. const String kSplitDebugInfo = 'SplitDebugInfo'; /// Alternative scheme for file URIs. /// /// May be used along with [kFileSystemRoots] to support a multi-root /// filesystem. const String kFileSystemScheme = 'FileSystemScheme'; /// Additional filesystem roots. /// /// If provided, must be used along with [kFileSystemScheme]. const String 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. /// /// The other supported value is armv7, the 32-bit iOS architecture. const String 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. /// /// Supported values are x86_64 and arm64. const String kDarwinArchs = 'DarwinArchs'; /// Path to the SDK root to be used as the isysroot. const String kSdkRoot = 'SdkRoot'; /// Whether to enable Dart obfuscation and where to save the symbol map. const String kDartObfuscation = 'DartObfuscation'; /// An output directory where one or more code-size measurements may be written. const String 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 String kCodesignIdentity = 'CodesignIdentity'; /// The build define controlling whether icon fonts should be stripped down to /// only the glyphs used by the application. const String kIconTreeShakerFlag = 'TreeShakeIcons'; /// The input key for an SkSL bundle path. const String kBundleSkSLPath = 'BundleSkSLPath'; 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(); } /// The null safety runtime mode the app should be built in. enum NullSafetyMode { sound, unsound, /// The null safety mode was not detected. Only supported for 'flutter test'. autodetect, } String _getCurrentHostPlatformArchName() { final HostPlatform hostPlatform = getCurrentHostPlatform(); return getNameForHostPlatformArch(hostPlatform); } String getNameForTargetPlatformArch(TargetPlatform platform) { switch (platform) { case TargetPlatform.linux_x64: case TargetPlatform.darwin: case TargetPlatform.windows_x64: return 'x64'; case TargetPlatform.linux_arm64: return 'arm64'; default: throw UnsupportedError('Unexpected target platform $platform'); } } String getNameForHostPlatformArch(HostPlatform platform) { switch (platform) { case HostPlatform.darwin_x64: return 'x64'; case HostPlatform.darwin_arm: return 'arm'; case HostPlatform.linux_x64: return 'x64'; case HostPlatform.linux_arm64: return 'arm64'; case HostPlatform.windows_x64: return 'x64'; } }