build_info.dart 32.7 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_null_migrated.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 = '.packages', // TODO(jonahwilliams): 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 46
  }) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[],
       extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[],
47
       fileSystemRoots = fileSystemRoots ?? const <String>[],
48 49
       dartDefines = dartDefines ?? const <String>[],
       dartExperiments = dartExperiments ?? const <String>[];
50 51

  final BuildMode mode;
52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

162 163 164 165
  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);
166 167 168 169 170 171 172 173

  /// 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.
  ///
174 175
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
176
  bool get isProfile => mode == BuildMode.profile;
177 178 179

  /// Returns whether a release build is requested.
  ///
180 181
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
182
  bool get isRelease => mode == BuildMode.release;
183

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

190 191 192 193
  bool get usesAot => isAotBuildMode(mode);
  bool get supportsEmulator => isEmulatorBuildMode(mode);
  bool get supportsSimulator => isEmulatorBuildMode(mode);
  String get modeName => getModeName(mode);
194
  String get friendlyModeName => getFriendlyModeName(mode);
195

196
  /// the flavor name in the output files is lower-cased (see flutter.gradle),
197
  /// so the lower cased flavor name is used to compute the output file name
198
  String? get lowerCasedFlavor => flavor?.toLowerCase();
199

200 201 202 203 204 205 206 207 208 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
  /// 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!,
    };
  }

234
  /// Convert to a structured string encoded structure appropriate for usage as
235 236
  /// environment variables or to embed in other scripts.
  ///
237
  /// Fields that are `null` are excluded from this configuration.
238 239
  Map<String, String> toEnvironmentConfig() {
    return <String, String>{
240
      if (dartDefines.isNotEmpty)
241
        'DART_DEFINES': encodeDartDefines(dartDefines),
242 243
      if (dartObfuscation != null)
        'DART_OBFUSCATION': dartObfuscation.toString(),
244 245 246 247
      if (extraFrontEndOptions.isNotEmpty)
        'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','),
      if (extraGenSnapshotOptions.isNotEmpty)
        'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','),
248
      if (splitDebugInfoPath != null)
249
        'SPLIT_DEBUG_INFO': splitDebugInfoPath!,
250 251 252 253
      if (trackWidgetCreation != null)
        'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(),
      if (treeShakeIcons != null)
        'TREE_SHAKE_ICONS': treeShakeIcons.toString(),
254
      if (performanceMeasurementFile != null)
255
        'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile!,
256
      if (bundleSkSLPath != null)
257
        'BUNDLE_SKSL_PATH': bundleSkSLPath!,
258 259
      if (packagesPath != null)
        'PACKAGE_CONFIG': packagesPath,
260
      if (codeSizeDirectory != null)
261
        'CODE_SIZE_DIRECTORY': codeSizeDirectory!,
262 263
    };
  }
264 265 266 267 268 269

  /// 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>[
270
      if (dartDefines.isNotEmpty)
271 272 273
        '-Pdart-defines=${encodeDartDefines(dartDefines)}',
      if (dartObfuscation != null)
        '-Pdart-obfuscation=$dartObfuscation',
274 275 276 277
      if (extraFrontEndOptions.isNotEmpty)
        '-Pextra-front-end-options=${extraFrontEndOptions.join(',')}',
      if (extraGenSnapshotOptions.isNotEmpty)
        '-Pextra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}',
278 279 280 281 282 283 284 285 286 287 288 289
      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',
290 291
      for (String projectArg in androidProjectArgs)
        '-P$projectArg',
292 293
    ];
  }
294 295 296 297 298 299 300 301 302
}

/// 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,
303
      AndroidArch.x86_64,
304 305
    ],
    this.splitPerAbi = false,
306
    this.fastStart = false,
307
  });
308

309 310 311 312 313 314 315 316 317 318 319 320
  // 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;
321 322 323

  /// Whether to bootstrap an empty application.
  final bool fastStart;
324 325
}

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 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
/// 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);
376

377
  /// Whether this mode is using the JIT runtime.
378 379 380 381 382 383 384 385 386 387 388
  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;
}
389 390 391

/// Return the name for the build mode, or "any" if null.
String getNameForBuildMode(BuildMode buildMode) {
392
  return buildMode.name;
393 394 395 396
}

/// Returns the [BuildMode] for a particular `name`.
BuildMode getBuildModeForName(String name) {
397
  return BuildMode.fromName(name);
398 399
}

400 401 402 403 404 405
/// Environment type of the target device.
enum EnvironmentType {
  physical,
  simulator,
}

406
String? validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String? buildNumber, Logger logger) {
407 408 409 410
  if (buildNumber == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
411
      targetPlatform == TargetPlatform.darwin) {
412 413 414
    // 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, '');
415 416 417
    if (tmpBuildNumber.isEmpty) {
      return null;
    }
418 419 420 421 422 423 424 425 426
    final List<String> segments = tmpBuildNumber
        .split('.')
        .where((String segment) => segment.isNotEmpty)
        .toList();
    if (segments.isEmpty) {
      segments.add('0');
    }
    tmpBuildNumber = segments.join('.');
    if (tmpBuildNumber != buildNumber) {
427
      logger.printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
          '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) {
445
      logger.printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
446 447 448 449 450 451 452
          'See versionCode at https://developer.android.com/studio/publish/versioning');
    }
    return tmpBuildNumberStr;
  }
  return buildNumber;
}

453
String? validatedBuildNameForPlatform(TargetPlatform targetPlatform, String? buildName, Logger logger) {
454 455 456 457
  if (buildName == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
458
      targetPlatform == TargetPlatform.darwin) {
459 460 461
    // 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, '');
462 463 464
    if (tmpBuildName.isEmpty) {
      return null;
    }
465 466 467 468 469 470 471 472 473
    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) {
474
      logger.printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
475 476 477 478
          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
    }
    return tmpBuildName;
  }
479 480
  if (targetPlatform == TargetPlatform.android ||
      targetPlatform == TargetPlatform.android_arm ||
481 482 483 484 485 486 487 488 489
      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;
}

490
String getModeName(BuildMode mode) => getEnumName(mode);
491

492 493 494 495
String getFriendlyModeName(BuildMode mode) {
  return snakeCase(getModeName(mode)).replaceAll('_', ' ');
}

496 497 498 499 500
// Returns true if the selected build mode uses ahead-of-time compilation.
bool isAotBuildMode(BuildMode mode) {
  return mode == BuildMode.profile || mode == BuildMode.release;
}

501
// Returns true if the given build mode can be used on emulators / simulators.
502
bool isEmulatorBuildMode(BuildMode mode) {
503
  return mode == BuildMode.debug;
504
}
505

506
enum TargetPlatform {
507
  android,
508
  ios,
509
  darwin,
510
  linux_x64,
511
  linux_arm64,
512
  windows_x64,
513
  windows_uwp_x64,
514 515
  fuchsia_arm64,
  fuchsia_x64,
516
  tester,
517
  web_javascript,
518
  // The arch specific android target platforms are soft-deprecated.
519 520 521 522 523 524 525
  // 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,
526 527
}

528
/// iOS and macOS target device architecture.
529 530
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
531
enum DarwinArch {
532 533
  armv7,
  arm64,
534
  x86_64,
535 536
}

537
// TODO(jonahwilliams): replace all android TargetPlatform usage with AndroidArch.
538 539 540 541 542 543 544
enum AndroidArch {
  armeabi_v7a,
  arm64_v8a,
  x86,
  x86_64,
}

545
/// The default set of iOS device architectures to build for.
546 547 548 549 550 551 552
List<DarwinArch> defaultIOSArchsForEnvironment(
    EnvironmentType environmentType) {
  if (environmentType == EnvironmentType.simulator) {
    return <DarwinArch>[
      // Apple Silicon ARM simulators not yet supported.
      DarwinArch.x86_64,
    ];
553
  }
554 555 556 557
  return <DarwinArch>[
    DarwinArch.armv7,
    DarwinArch.arm64,
  ];
558
}
559

560
String getNameForDarwinArch(DarwinArch arch) {
561
  switch (arch) {
562
    case DarwinArch.armv7:
563
      return 'armv7';
564
    case DarwinArch.arm64:
565
      return 'arm64';
566 567
    case DarwinArch.x86_64:
      return 'x86_64';
568 569 570
  }
}

571
DarwinArch getIOSArchForName(String arch) {
572 573
  switch (arch) {
    case 'armv7':
574
    case 'armv7f': // iPhone 4S.
575
    case 'armv7s': // iPad 4.
576
      return DarwinArch.armv7;
577
    case 'arm64':
578
    case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices.
579
      return DarwinArch.arm64;
580 581
    case 'x86_64':
      return DarwinArch.x86_64;
582
  }
583
  throw Exception('Unsupported iOS arch name "$arch"');
584 585
}

586 587 588 589 590 591 592 593 594 595
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"');
}

596
String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) {
597 598
  switch (platform) {
    case TargetPlatform.android_arm:
599
      return 'android-arm';
600 601
    case TargetPlatform.android_arm64:
      return 'android-arm64';
602
    case TargetPlatform.android_x64:
603
      return 'android-x64';
604
    case TargetPlatform.android_x86:
605
      return 'android-x86';
606
    case TargetPlatform.ios:
607 608 609
      if (darwinArch != null) {
        return 'ios-${getNameForDarwinArch(darwinArch)}';
      }
610
      return 'ios';
611 612 613 614 615
    case TargetPlatform.darwin:
      if (darwinArch != null) {
        return 'darwin-${getNameForDarwinArch(darwinArch)}';
      }
      return 'darwin';
616
    case TargetPlatform.linux_x64:
617
      return 'linux-x64';
618
    case TargetPlatform.linux_arm64:
619
      return 'linux-arm64';
620
    case TargetPlatform.windows_x64:
621
      return 'windows-x64';
622 623
    case TargetPlatform.windows_uwp_x64:
      return 'windows-uwp-x64';
624 625 626 627
    case TargetPlatform.fuchsia_arm64:
      return 'fuchsia-arm64';
    case TargetPlatform.fuchsia_x64:
      return 'fuchsia-x64';
628 629
    case TargetPlatform.tester:
      return 'flutter-tester';
630 631
    case TargetPlatform.web_javascript:
      return 'web-javascript';
632 633
    case TargetPlatform.android:
      return 'android';
634
  }
635 636
}

637
TargetPlatform? getTargetPlatformForName(String platform) {
638
  switch (platform) {
639 640
    case 'android':
      return TargetPlatform.android;
641 642
    case 'android-arm':
      return TargetPlatform.android_arm;
643 644
    case 'android-arm64':
      return TargetPlatform.android_arm64;
645 646 647 648
    case 'android-x64':
      return TargetPlatform.android_x64;
    case 'android-x86':
      return TargetPlatform.android_x86;
649 650 651 652
    case 'fuchsia-arm64':
      return TargetPlatform.fuchsia_arm64;
    case 'fuchsia-x64':
      return TargetPlatform.fuchsia_x64;
653 654
    case 'ios':
      return TargetPlatform.ios;
655 656 657
    case 'darwin':
    // For backward-compatibility and also for Tester, where it must match
    // host platform name (HostPlatform.darwin_x64)
658
    case 'darwin-x64':
659 660
    case 'darwin-arm':
      return TargetPlatform.darwin;
661
    case 'linux-x64':
662
      return TargetPlatform.linux_x64;
663 664
   case 'linux-arm64':
      return TargetPlatform.linux_arm64;
665
    case 'windows-x64':
666
      return TargetPlatform.windows_x64;
667 668
    case 'windows-uwp-x64':
      return TargetPlatform.windows_uwp_x64;
669 670
    case 'web-javascript':
      return TargetPlatform.web_javascript;
671
  }
pq's avatar
pq committed
672
  assert(platform != null);
673 674 675
  return null;
}

676 677 678 679 680 681 682 683 684 685 686
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;
  }
687
  throw Exception('Unsupported Android arch name "$platform"');
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
}

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

716 717 718 719 720 721 722
String fuchsiaArchForTargetPlatform(TargetPlatform targetPlatform) {
  switch (targetPlatform) {
    case TargetPlatform.fuchsia_arm64:
      return 'arm64';
    case TargetPlatform.fuchsia_x64:
      return 'x64';
    default:
723
      throw UnsupportedError('Unexpected Fuchsia platform $targetPlatform');
724 725 726
  }
}

727
HostPlatform getCurrentHostPlatform() {
728
  if (globals.platform.isMacOS) {
729
    return HostPlatform.darwin_x64;
730
  }
731
  if (globals.platform.isLinux) {
732 733
    // support x64 and arm64 architecture.
    return globals.os.hostPlatform;
734
  }
735
  if (globals.platform.isWindows) {
736
    return HostPlatform.windows_x64;
737
  }
738

739
  globals.printError('Unsupported host platform, defaulting to Linux');
740 741

  return HostPlatform.linux_x64;
742
}
743 744

/// Returns the top-level build output directory.
745
String getBuildDirectory([Config? config, FileSystem? fileSystem]) {
746 747
  // TODO(johnmccutchan): Stop calling this function as part of setting
  // up command line argument processing.
748 749 750 751 752 753
  if (context == null) {
    return 'build';
  }
  final Config localConfig = config ?? globals.config;
  final FileSystem localFilesystem = fileSystem ?? globals.fs;
  if (localConfig == null) {
754
    return 'build';
755
  }
756

757
  final String buildDir = localConfig.getValue('build-dir') as String? ?? 'build';
758
  if (localFilesystem.path.isAbsolute(buildDir)) {
759
    throw Exception(
760
        'build-dir config setting in ${globals.config.configPath} must be relative');
761 762 763 764 765 766
  }
  return buildDir;
}

/// Returns the Android build output directory.
String getAndroidBuildDirectory() {
767
  // TODO(cbracken): move to android subdir.
768 769 770 771 772
  return getBuildDirectory();
}

/// Returns the AOT build output directory.
String getAotBuildDirectory() {
773
  return globals.fs.path.join(getBuildDirectory(), 'aot');
774 775 776 777
}

/// Returns the asset build output directory.
String getAssetBuildDirectory() {
778
  return globals.fs.path.join(getBuildDirectory(), 'flutter_assets');
779 780 781 782
}

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

786 787
/// Returns the macOS build output directory.
String getMacOSBuildDirectory() {
788
  return globals.fs.path.join(getBuildDirectory(), 'macos');
789 790
}

791 792
/// Returns the web build output directory.
String getWebBuildDirectory() {
793
  return globals.fs.path.join(getBuildDirectory(), 'web');
794 795
}

796
/// Returns the Linux build output directory.
797
String getLinuxBuildDirectory([TargetPlatform? targetPlatform]) {
798 799 800
  final String arch = (targetPlatform == null) ?
      _getCurrentHostPlatformArchName() :
      getNameForTargetPlatformArch(targetPlatform);
801
  final String subDirs = 'linux/$arch';
802
  return globals.fs.path.join(getBuildDirectory(), subDirs);
803 804
}

805 806
/// Returns the Windows build output directory.
String getWindowsBuildDirectory() {
807
  return globals.fs.path.join(getBuildDirectory(), 'windows');
808 809
}

810 811 812 813 814
/// Returns the Windows UWP build output directory.
String getWindowsBuildUwpDirectory() {
  return globals.fs.path.join(getBuildDirectory(), 'winuwp');
}

815 816
/// Returns the Fuchsia build output directory.
String getFuchsiaBuildDirectory() {
817
  return globals.fs.path.join(getBuildDirectory(), 'fuchsia');
818
}
819 820 821 822 823 824

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

825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 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
/// 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, defautls 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';

906 907 908 909 910 911 912 913 914 915
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
916
/// user provided commands will still be encoded.
917 918 919
///
/// If the presence of the `/` character ends up being an issue, this can
/// be changed to use base32 instead.
920
String encodeDartDefines(List<String> defines) {
921 922 923 924
  return defines.map(_defineEncoder.convert).join(',');
}

List<String> decodeCommaSeparated(Map<String, String> environmentDefines, String key) {
925
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
926 927
    return <String>[];
  }
928
  return environmentDefines[key]!
929 930 931
    .split(',')
    .cast<String>()
    .toList();
932 933 934
}

/// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
935
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
936
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
937
    return <String>[];
938
  }
939
  return environmentDefines[key]!
940
    .split(',')
941
    .map<Object>(_defineDecoder.convert)
942 943 944
    .cast<String>()
    .toList();
}
945 946 947 948 949

/// The null safety runtime mode the app should be built in.
enum NullSafetyMode {
  sound,
  unsound,
950
  /// The null safety mode was not detected. Only supported for 'flutter test'.
951 952
  autodetect,
}
953 954 955 956 957 958 959 960 961

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

String getNameForTargetPlatformArch(TargetPlatform platform) {
  switch (platform) {
    case TargetPlatform.linux_x64:
962
    case TargetPlatform.darwin:
963 964 965 966 967
    case TargetPlatform.windows_x64:
      return 'x64';
    case TargetPlatform.linux_arm64:
      return 'arm64';
    default:
968
      throw UnsupportedError('Unexpected target platform $platform');
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
  }
}

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