build_info.dart 35.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:package_config/package_config_types.dart';
6

7
import 'base/config.dart';
8
import 'base/context.dart';
9
import 'base/file_system.dart';
10
import 'base/logger.dart';
11
import 'base/os.dart';
12
import 'base/utils.dart';
13
import 'convert.dart';
14
import 'globals.dart' as globals;
15

16 17 18
/// Whether icon font subsetting is enabled by default.
const bool kIconTreeShakerEnabledDefault = true;

19 20
/// Information about a build to be performed or used.
class BuildInfo {
21 22 23
  const BuildInfo(
    this.mode,
    this.flavor, {
24
    this.trackWidgetCreation = false,
25 26
    List<String>? extraFrontEndOptions,
    List<String>? extraGenSnapshotOptions,
27
    List<String>? fileSystemRoots,
28
    this.androidProjectArgs = const <String>[],
29
    this.fileSystemScheme,
30 31
    this.buildNumber,
    this.buildName,
32
    this.splitDebugInfoPath,
33
    this.dartObfuscation = false,
34
    List<String>? dartDefines,
35
    this.bundleSkSLPath,
36 37
    List<String>? dartExperiments,
    required this.treeShakeIcons,
38
    this.performanceMeasurementFile,
39
    this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default.
40
    this.nullSafetyMode = NullSafetyMode.sound,
41
    this.codeSizeDirectory,
42
    this.androidGradleDaemon = true,
43
    this.packageConfig = PackageConfig.empty,
44
    this.initializeFromDill,
45
    this.assumeInitializeFromDillUpToDate = false,
46 47
  }) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[],
       extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[],
48
       fileSystemRoots = fileSystemRoots ?? const <String>[],
49 50
       dartDefines = dartDefines ?? const <String>[],
       dartExperiments = dartExperiments ?? const <String>[];
51 52

  final BuildMode mode;
53

54 55 56 57 58
  /// The null safety mode the application should be run in.
  ///
  /// If not provided, defaults to [NullSafetyMode.autodetect].
  final NullSafetyMode nullSafetyMode;

59
  /// Whether the build should subset icon fonts.
60 61
  final bool treeShakeIcons;

62 63 64 65 66 67
  /// 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).
68
  final String? flavor;
69

70
  /// The path to the package configuration file to use for compilation.
71 72
  ///
  /// This is used by package:package_config to locate the actual package_config.json
73
  /// file. If not provided, defaults to `.dart_tool/packages`.
74 75
  final String packagesPath;

76
  final List<String> fileSystemRoots;
77
  final String? fileSystemScheme;
78

79 80 81
  /// Whether the build should track widget creation locations.
  final bool trackWidgetCreation;

82
  /// Extra command-line options for front-end.
83
  final List<String> extraFrontEndOptions;
84 85

  /// Extra command-line options for gen_snapshot.
86
  final List<String> extraGenSnapshotOptions;
87

88 89 90 91 92
  /// 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.
93
  final String? buildNumber;
94 95 96 97

  /// 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.
98
  /// On Xcode builds it is used as CFBundleShortVersionString.
99
  final String? buildName;
100

101 102 103
  /// An optional directory path to save debugging information from dwarf stack
  /// traces. If null, stack trace information is not stripped from the
  /// executable.
104
  final String? splitDebugInfoPath;
105

106 107 108
  /// Whether to apply dart source code obfuscation.
  final bool dartObfuscation;

109
  /// An optional path to a JSON containing object SkSL shaders.
110 111
  ///
  /// Currently this is only supported for Android builds.
112
  final String? bundleSkSLPath;
113

114 115 116 117 118 119
  /// 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;

120 121 122
  /// A list of Dart experiments.
  final List<String> dartExperiments;

123 124 125 126 127
  /// 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.
128
  final String? performanceMeasurementFile;
129

130
  /// If provided, an output directory where one or more v8-style heap snapshots
131
  /// will be written for code size profiling.
132
  final String? codeSizeDirectory;
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147
  /// 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;

148 149 150 151
  /// Additional key value pairs that are passed directly to the gradle project via the `-P`
  /// flag.
  final List<String> androidProjectArgs;

152 153 154 155 156 157
  /// 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;

158 159 160 161 162
  /// 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;

163 164 165 166
  /// 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;

167
  static const BuildInfo debug = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false);
168 169 170
  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);
171 172 173 174 175 176 177 178

  /// 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.
  ///
179 180
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
181
  bool get isProfile => mode == BuildMode.profile;
182 183 184

  /// Returns whether a release build is requested.
  ///
185 186
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
187
  bool get isRelease => mode == BuildMode.release;
188

189 190 191 192 193 194
  /// Returns whether a JIT release build is requested.
  ///
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
  bool get isJitRelease => mode == BuildMode.jitRelease;

195 196 197 198
  bool get usesAot => isAotBuildMode(mode);
  bool get supportsEmulator => isEmulatorBuildMode(mode);
  bool get supportsSimulator => isEmulatorBuildMode(mode);
  String get modeName => getModeName(mode);
199
  String get friendlyModeName => getFriendlyModeName(mode);
200

201
  /// the flavor name in the output apk files is lower-cased (see flutter.gradle),
202
  /// so the lower cased flavor name is used to compute the output file name
203
  String? get lowerCasedFlavor => flavor?.toLowerCase();
204

205 206 207 208
  /// 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);

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  /// 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!,
240 241 242 243
      if (buildName != null)
        kBuildName: buildName!,
      if (buildNumber != null)
        kBuildNumber: buildNumber!,
244 245 246
    };
  }

247
  /// Convert to a structured string encoded structure appropriate for usage as
248 249
  /// environment variables or to embed in other scripts.
  ///
250
  /// Fields that are `null` are excluded from this configuration.
251 252
  Map<String, String> toEnvironmentConfig() {
    return <String, String>{
253
      if (dartDefines.isNotEmpty)
254
        'DART_DEFINES': encodeDartDefines(dartDefines),
255 256
      if (dartObfuscation != null)
        'DART_OBFUSCATION': dartObfuscation.toString(),
257 258 259 260
      if (extraFrontEndOptions.isNotEmpty)
        'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','),
      if (extraGenSnapshotOptions.isNotEmpty)
        'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','),
261
      if (splitDebugInfoPath != null)
262
        'SPLIT_DEBUG_INFO': splitDebugInfoPath!,
263 264 265 266
      if (trackWidgetCreation != null)
        'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(),
      if (treeShakeIcons != null)
        'TREE_SHAKE_ICONS': treeShakeIcons.toString(),
267
      if (performanceMeasurementFile != null)
268
        'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile!,
269
      if (bundleSkSLPath != null)
270
        'BUNDLE_SKSL_PATH': bundleSkSLPath!,
271 272
      if (packagesPath != null)
        'PACKAGE_CONFIG': packagesPath,
273
      if (codeSizeDirectory != null)
274
        'CODE_SIZE_DIRECTORY': codeSizeDirectory!,
275 276
    };
  }
277 278 279 280 281 282

  /// 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>[
283
      if (dartDefines.isNotEmpty)
284 285 286
        '-Pdart-defines=${encodeDartDefines(dartDefines)}',
      if (dartObfuscation != null)
        '-Pdart-obfuscation=$dartObfuscation',
287 288 289 290
      if (extraFrontEndOptions.isNotEmpty)
        '-Pextra-front-end-options=${extraFrontEndOptions.join(',')}',
      if (extraGenSnapshotOptions.isNotEmpty)
        '-Pextra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}',
291 292 293 294 295 296 297 298 299 300 301 302
      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',
303 304
      for (String projectArg in androidProjectArgs)
        '-P$projectArg',
305 306
    ];
  }
307 308 309 310 311 312 313 314 315
}

/// 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,
316
      AndroidArch.x86_64,
317 318
    ],
    this.splitPerAbi = false,
319
    this.fastStart = false,
320
    this.multidexEnabled = false,
321
  });
322

323 324 325 326 327 328 329 330 331 332 333 334
  // 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;
335 336 337

  /// Whether to bootstrap an empty application.
  final bool fastStart;
338 339 340

  /// Whether to enable multidex support for apps with more than 64k methods.
  final bool multidexEnabled;
341 342
}

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
/// 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);
393

394
  /// Whether this mode is using the JIT runtime.
395 396 397 398 399 400 401 402 403 404 405
  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;
}
406 407 408

/// Return the name for the build mode, or "any" if null.
String getNameForBuildMode(BuildMode buildMode) {
409
  return buildMode.name;
410 411 412 413
}

/// Returns the [BuildMode] for a particular `name`.
BuildMode getBuildModeForName(String name) {
414
  return BuildMode.fromName(name);
415 416
}

417 418 419 420 421 422
/// Environment type of the target device.
enum EnvironmentType {
  physical,
  simulator,
}

423
String? validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String? buildNumber, Logger logger) {
424 425 426 427
  if (buildNumber == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
428
      targetPlatform == TargetPlatform.darwin) {
429 430 431
    // 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, '');
432 433 434
    if (tmpBuildNumber.isEmpty) {
      return null;
    }
435 436 437 438 439 440 441 442 443
    final List<String> segments = tmpBuildNumber
        .split('.')
        .where((String segment) => segment.isNotEmpty)
        .toList();
    if (segments.isEmpty) {
      segments.add('0');
    }
    tmpBuildNumber = segments.join('.');
    if (tmpBuildNumber != buildNumber) {
444
      logger.printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
          '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) {
462
      logger.printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
463 464 465 466 467 468 469
          'See versionCode at https://developer.android.com/studio/publish/versioning');
    }
    return tmpBuildNumberStr;
  }
  return buildNumber;
}

470
String? validatedBuildNameForPlatform(TargetPlatform targetPlatform, String? buildName, Logger logger) {
471 472 473 474
  if (buildName == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
475
      targetPlatform == TargetPlatform.darwin) {
476 477 478
    // 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, '');
479 480 481
    if (tmpBuildName.isEmpty) {
      return null;
    }
482 483 484 485 486 487 488 489 490
    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) {
491
      logger.printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
492 493 494 495
          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
    }
    return tmpBuildName;
  }
496 497
  if (targetPlatform == TargetPlatform.android ||
      targetPlatform == TargetPlatform.android_arm ||
498 499 500 501 502 503 504 505 506
      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;
}

507
String getModeName(BuildMode mode) => getEnumName(mode);
508

509 510 511 512
String getFriendlyModeName(BuildMode mode) {
  return snakeCase(getModeName(mode)).replaceAll('_', ' ');
}

513 514 515 516 517
// Returns true if the selected build mode uses ahead-of-time compilation.
bool isAotBuildMode(BuildMode mode) {
  return mode == BuildMode.profile || mode == BuildMode.release;
}

518
// Returns true if the given build mode can be used on emulators / simulators.
519
bool isEmulatorBuildMode(BuildMode mode) {
520
  return mode == BuildMode.debug;
521
}
522

523
enum TargetPlatform {
524
  android,
525
  ios,
526
  darwin,
527
  linux_x64,
528
  linux_arm64,
529
  windows_x64,
530
  windows_uwp_x64,
531 532
  fuchsia_arm64,
  fuchsia_x64,
533
  tester,
534
  web_javascript,
535
  // The arch specific android target platforms are soft-deprecated.
536 537 538 539 540 541 542
  // 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,
543 544
}

545
/// iOS and macOS target device architecture.
546 547
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
548
enum DarwinArch {
549 550
  armv7,
  arm64,
551
  x86_64,
552 553
}

554
// TODO(zanderso): replace all android TargetPlatform usage with AndroidArch.
555 556 557 558 559 560 561
enum AndroidArch {
  armeabi_v7a,
  arm64_v8a,
  x86,
  x86_64,
}

562
/// The default set of iOS device architectures to build for.
563 564 565 566 567
List<DarwinArch> defaultIOSArchsForEnvironment(
    EnvironmentType environmentType) {
  if (environmentType == EnvironmentType.simulator) {
    return <DarwinArch>[
      DarwinArch.x86_64,
568
      DarwinArch.arm64,
569
    ];
570
  }
571 572 573 574
  return <DarwinArch>[
    DarwinArch.armv7,
    DarwinArch.arm64,
  ];
575
}
576

577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
// 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 getDartNameForDarwinArch(DarwinArch arch) {
  switch (arch) {
    case DarwinArch.armv7:
      return 'armv7';
    case DarwinArch.arm64:
      return 'arm64';
    case DarwinArch.x86_64:
      return 'x64';
  }
}

// Returns Apple's name for the specified target architecture.
//
// When invoking Apple tools such as `xcodebuild` or `lipo`, the tool often
// passes one or more target architectures as paramters. The names returned by
// this function reflect Apple's name for the specified architecture.
//
// For consistency with developer expectations, Flutter outputs also use these
// architecture names in its build products for Darwin target platforms.
602
String getNameForDarwinArch(DarwinArch arch) {
603
  switch (arch) {
604
    case DarwinArch.armv7:
605
      return 'armv7';
606
    case DarwinArch.arm64:
607
      return 'arm64';
608 609
    case DarwinArch.x86_64:
      return 'x86_64';
610 611 612
  }
}

613
DarwinArch getIOSArchForName(String arch) {
614 615
  switch (arch) {
    case 'armv7':
616
    case 'armv7f': // iPhone 4S.
617
    case 'armv7s': // iPad 4.
618
      return DarwinArch.armv7;
619
    case 'arm64':
620
    case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices.
621
      return DarwinArch.arm64;
622 623
    case 'x86_64':
      return DarwinArch.x86_64;
624
  }
625
  throw Exception('Unsupported iOS arch name "$arch"');
626 627
}

628 629 630 631 632 633 634 635 636 637
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"');
}

638
String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) {
639 640
  switch (platform) {
    case TargetPlatform.android_arm:
641
      return 'android-arm';
642 643
    case TargetPlatform.android_arm64:
      return 'android-arm64';
644
    case TargetPlatform.android_x64:
645
      return 'android-x64';
646
    case TargetPlatform.android_x86:
647
      return 'android-x86';
648
    case TargetPlatform.ios:
649 650 651
      if (darwinArch != null) {
        return 'ios-${getNameForDarwinArch(darwinArch)}';
      }
652
      return 'ios';
653 654 655 656 657
    case TargetPlatform.darwin:
      if (darwinArch != null) {
        return 'darwin-${getNameForDarwinArch(darwinArch)}';
      }
      return 'darwin';
658
    case TargetPlatform.linux_x64:
659
      return 'linux-x64';
660
    case TargetPlatform.linux_arm64:
661
      return 'linux-arm64';
662
    case TargetPlatform.windows_x64:
663
      return 'windows-x64';
664 665
    case TargetPlatform.windows_uwp_x64:
      return 'windows-uwp-x64';
666 667 668 669
    case TargetPlatform.fuchsia_arm64:
      return 'fuchsia-arm64';
    case TargetPlatform.fuchsia_x64:
      return 'fuchsia-x64';
670 671
    case TargetPlatform.tester:
      return 'flutter-tester';
672 673
    case TargetPlatform.web_javascript:
      return 'web-javascript';
674 675
    case TargetPlatform.android:
      return 'android';
676
  }
677 678
}

679
TargetPlatform getTargetPlatformForName(String platform) {
680
  switch (platform) {
681 682
    case 'android':
      return TargetPlatform.android;
683 684
    case 'android-arm':
      return TargetPlatform.android_arm;
685 686
    case 'android-arm64':
      return TargetPlatform.android_arm64;
687 688 689 690
    case 'android-x64':
      return TargetPlatform.android_x64;
    case 'android-x86':
      return TargetPlatform.android_x86;
691 692 693 694
    case 'fuchsia-arm64':
      return TargetPlatform.fuchsia_arm64;
    case 'fuchsia-x64':
      return TargetPlatform.fuchsia_x64;
695 696
    case 'ios':
      return TargetPlatform.ios;
697 698 699
    case 'darwin':
    // For backward-compatibility and also for Tester, where it must match
    // host platform name (HostPlatform.darwin_x64)
700
    case 'darwin-x64':
701 702
    case 'darwin-arm':
      return TargetPlatform.darwin;
703
    case 'linux-x64':
704
      return TargetPlatform.linux_x64;
705 706
   case 'linux-arm64':
      return TargetPlatform.linux_arm64;
707
    case 'windows-x64':
708
      return TargetPlatform.windows_x64;
709 710
    case 'windows-uwp-x64':
      return TargetPlatform.windows_uwp_x64;
711 712
    case 'web-javascript':
      return TargetPlatform.web_javascript;
713
  }
714
  throw Exception('Unsupported platform name "$platform"');
715 716
}

717 718 719 720 721 722 723 724 725 726 727
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;
  }
728
  throw Exception('Unsupported Android arch name "$platform"');
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
}

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';
  }
}

757 758 759 760 761 762
String fuchsiaArchForTargetPlatform(TargetPlatform targetPlatform) {
  switch (targetPlatform) {
    case TargetPlatform.fuchsia_arm64:
      return 'arm64';
    case TargetPlatform.fuchsia_x64:
      return 'x64';
763 764 765 766 767 768 769 770 771 772 773 774 775
    case TargetPlatform.android:
    case TargetPlatform.android_arm:
    case TargetPlatform.android_arm64:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
    case TargetPlatform.darwin:
    case TargetPlatform.ios:
    case TargetPlatform.linux_arm64:
    case TargetPlatform.linux_x64:
    case TargetPlatform.tester:
    case TargetPlatform.web_javascript:
    case TargetPlatform.windows_uwp_x64:
    case TargetPlatform.windows_x64:
776
      throw UnsupportedError('Unexpected Fuchsia platform $targetPlatform');
777 778 779
  }
}

780
HostPlatform getCurrentHostPlatform() {
781
  if (globals.platform.isMacOS) {
782
    return HostPlatform.darwin_x64;
783
  }
784
  if (globals.platform.isLinux) {
785 786
    // support x64 and arm64 architecture.
    return globals.os.hostPlatform;
787
  }
788
  if (globals.platform.isWindows) {
789
    return HostPlatform.windows_x64;
790
  }
791

792
  globals.printWarning('Unsupported host platform, defaulting to Linux');
793 794

  return HostPlatform.linux_x64;
795
}
796 797

/// Returns the top-level build output directory.
798
String getBuildDirectory([Config? config, FileSystem? fileSystem]) {
799 800
  // TODO(johnmccutchan): Stop calling this function as part of setting
  // up command line argument processing.
801 802 803 804 805 806
  if (context == null) {
    return 'build';
  }
  final Config localConfig = config ?? globals.config;
  final FileSystem localFilesystem = fileSystem ?? globals.fs;
  if (localConfig == null) {
807
    return 'build';
808
  }
809

810
  final String buildDir = localConfig.getValue('build-dir') as String? ?? 'build';
811
  if (localFilesystem.path.isAbsolute(buildDir)) {
812
    throw Exception(
813
        'build-dir config setting in ${globals.config.configPath} must be relative');
814 815 816 817 818 819
  }
  return buildDir;
}

/// Returns the Android build output directory.
String getAndroidBuildDirectory() {
820
  // TODO(cbracken): move to android subdir.
821 822 823 824 825
  return getBuildDirectory();
}

/// Returns the AOT build output directory.
String getAotBuildDirectory() {
826
  return globals.fs.path.join(getBuildDirectory(), 'aot');
827 828 829 830
}

/// Returns the asset build output directory.
String getAssetBuildDirectory() {
831
  return globals.fs.path.join(getBuildDirectory(), 'flutter_assets');
832 833 834 835
}

/// Returns the iOS build output directory.
String getIosBuildDirectory() {
836
  return globals.fs.path.join(getBuildDirectory(), 'ios');
837
}
838

839 840
/// Returns the macOS build output directory.
String getMacOSBuildDirectory() {
841
  return globals.fs.path.join(getBuildDirectory(), 'macos');
842 843
}

844 845
/// Returns the web build output directory.
String getWebBuildDirectory() {
846
  return globals.fs.path.join(getBuildDirectory(), 'web');
847 848
}

849
/// Returns the Linux build output directory.
850
String getLinuxBuildDirectory([TargetPlatform? targetPlatform]) {
851 852 853
  final String arch = (targetPlatform == null) ?
      _getCurrentHostPlatformArchName() :
      getNameForTargetPlatformArch(targetPlatform);
854
  final String subDirs = 'linux/$arch';
855
  return globals.fs.path.join(getBuildDirectory(), subDirs);
856 857
}

858 859
/// Returns the Windows build output directory.
String getWindowsBuildDirectory() {
860
  return globals.fs.path.join(getBuildDirectory(), 'windows');
861 862
}

863 864 865 866 867
/// Returns the Windows UWP build output directory.
String getWindowsBuildUwpDirectory() {
  return globals.fs.path.join(getBuildDirectory(), 'winuwp');
}

868 869
/// Returns the Fuchsia build output directory.
String getFuchsiaBuildDirectory() {
870
  return globals.fs.path.join(getBuildDirectory(), 'fuchsia');
871
}
872 873 874 875 876 877

/// 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';

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
/// 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
932
/// provided, defaults to x86_64.
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
///
/// 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';
958 959 960 961 962 963

/// The define to pass build name
const String kBuildName = 'BuildName';

/// The define to pass build number
const String kBuildNumber = 'BuildNumber';
964

965 966 967 968 969 970 971 972 973 974
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
975
/// user provided commands will still be encoded.
976 977 978
///
/// If the presence of the `/` character ends up being an issue, this can
/// be changed to use base32 instead.
979
String encodeDartDefines(List<String> defines) {
980 981 982 983
  return defines.map(_defineEncoder.convert).join(',');
}

List<String> decodeCommaSeparated(Map<String, String> environmentDefines, String key) {
984
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
985 986
    return <String>[];
  }
987
  return environmentDefines[key]!
988 989 990
    .split(',')
    .cast<String>()
    .toList();
991 992 993
}

/// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
994
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
995
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
996
    return <String>[];
997
  }
998
  return environmentDefines[key]!
999
    .split(',')
1000
    .map<Object>(_defineDecoder.convert)
1001 1002 1003
    .cast<String>()
    .toList();
}
1004 1005 1006 1007 1008

/// The null safety runtime mode the app should be built in.
enum NullSafetyMode {
  sound,
  unsound,
1009
  /// The null safety mode was not detected. Only supported for 'flutter test'.
1010 1011
  autodetect,
}
1012 1013 1014 1015 1016 1017 1018 1019 1020

String _getCurrentHostPlatformArchName() {
  final HostPlatform hostPlatform = getCurrentHostPlatform();
  return getNameForHostPlatformArch(hostPlatform);
}

String getNameForTargetPlatformArch(TargetPlatform platform) {
  switch (platform) {
    case TargetPlatform.linux_x64:
1021
    case TargetPlatform.darwin:
1022 1023 1024 1025
    case TargetPlatform.windows_x64:
      return 'x64';
    case TargetPlatform.linux_arm64:
      return 'arm64';
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
    case TargetPlatform.android:
    case TargetPlatform.android_arm:
    case TargetPlatform.android_arm64:
    case TargetPlatform.android_x64:
    case TargetPlatform.android_x86:
    case TargetPlatform.fuchsia_arm64:
    case TargetPlatform.fuchsia_x64:
    case TargetPlatform.ios:
    case TargetPlatform.tester:
    case TargetPlatform.web_javascript:
    case TargetPlatform.windows_uwp_x64:
1037
      throw UnsupportedError('Unexpected target platform $platform');
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
  }
}

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';
  }
}
1055 1056 1057 1058 1059 1060 1061

String? _uncapitalize(String? s) {
  if (s == null || s.isEmpty) {
    return s;
  }
  return s.substring(0, 1).toLowerCase() + s.substring(1);
}