build_info.dart 36.5 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 'artifacts.dart';
8 9
import 'base/config.dart';
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
import 'web/compile.dart';
16

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

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

  final BuildMode mode;
57

58 59 60 61 62
  /// The null safety mode the application should be run in.
  ///
  /// If not provided, defaults to [NullSafetyMode.autodetect].
  final NullSafetyMode nullSafetyMode;

63
  /// Whether the build should subset icon fonts.
64 65
  final bool treeShakeIcons;

66 67 68 69 70 71
  /// 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).
72
  final String? flavor;
73

74
  /// The path to the package configuration file to use for compilation.
75 76
  ///
  /// This is used by package:package_config to locate the actual package_config.json
77
  /// file. If not provided, defaults to `.dart_tool/package_config.json`.
78 79
  final String packagesPath;

80
  final List<String> fileSystemRoots;
81
  final String? fileSystemScheme;
82

83 84 85
  /// Whether the build should track widget creation locations.
  final bool trackWidgetCreation;

86 87 88 89
  /// If provided, the frontend server will be started in JIT mode from this
  /// file.
  final String? frontendServerStarterPath;

90
  /// Extra command-line options for front-end.
91
  final List<String> extraFrontEndOptions;
92 93

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

96 97 98 99 100
  /// 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.
101
  /// On Windows it is used as the build suffix for the product and file versions.
102
  final String? buildNumber;
103 104 105 106

  /// 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.
107
  /// On Xcode builds it is used as CFBundleShortVersionString.
108
  /// On Windows it is used as the major, minor, and patch parts of the product and file versions.
109
  final String? buildName;
110

111 112 113
  /// An optional directory path to save debugging information from dwarf stack
  /// traces. If null, stack trace information is not stripped from the
  /// executable.
114
  final String? splitDebugInfoPath;
115

116 117 118
  /// Whether to apply dart source code obfuscation.
  final bool dartObfuscation;

119
  /// An optional path to a JSON containing object SkSL shaders.
120 121
  ///
  /// Currently this is only supported for Android builds.
122
  final String? bundleSkSLPath;
123

124 125 126 127 128 129
  /// 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;

130 131 132
  /// A list of Dart experiments.
  final List<String> dartExperiments;

133 134 135
  /// When compiling to web, which web renderer mode we are using (html, canvaskit, auto)
  final WebRendererMode webRenderer;

136 137 138 139 140
  /// 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.
141
  final String? performanceMeasurementFile;
142

143
  /// If provided, an output directory where one or more v8-style heap snapshots
144
  /// will be written for code size profiling.
145
  final String? codeSizeDirectory;
146

147 148 149 150 151 152 153 154 155 156 157 158 159 160
  /// 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;

161 162 163 164
  /// Additional key value pairs that are passed directly to the gradle project via the `-P`
  /// flag.
  final List<String> androidProjectArgs;

165 166 167 168 169 170
  /// 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;

171 172 173 174 175
  /// 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;

176 177 178 179
  /// 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;

180 181 182
  /// If set, builds native assets with `build.dart` from all packages.
  final bool buildNativeAssets;

183
  static const BuildInfo debug = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, treeShakeIcons: false);
184 185 186
  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);
187 188 189 190 191 192 193 194

  /// 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.
  ///
195 196
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
197
  bool get isProfile => mode == BuildMode.profile;
198 199 200

  /// Returns whether a release build is requested.
  ///
201 202
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
203
  bool get isRelease => mode == BuildMode.release;
204

205 206 207 208 209 210
  /// Returns whether a JIT release build is requested.
  ///
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
  bool get isJitRelease => mode == BuildMode.jitRelease;

211 212 213
  bool get usesAot => isAotBuildMode(mode);
  bool get supportsEmulator => isEmulatorBuildMode(mode);
  bool get supportsSimulator => isEmulatorBuildMode(mode);
214
  String get modeName => mode.cliName;
215
  String get friendlyModeName => getFriendlyModeName(mode);
216

217
  /// the flavor name in the output apk files is lower-cased (see Flutter Gradle Plugin),
218
  /// so the lower cased flavor name is used to compute the output file name
219
  String? get lowerCasedFlavor => flavor?.toLowerCase();
220

221 222 223 224
  /// 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);

225 226 227 228 229 230 231 232
  /// 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>{
233
      kBuildMode: mode.cliName,
234 235
      if (dartDefines.isNotEmpty)
        kDartDefines: encodeDartDefines(dartDefines),
236
      kDartObfuscation: dartObfuscation.toString(),
237 238
      if (frontendServerStarterPath != null)
        kFrontendServerStarterPath: frontendServerStarterPath!,
239 240 241 242 243 244
      if (extraFrontEndOptions.isNotEmpty)
        kExtraFrontEndOptions: extraFrontEndOptions.join(','),
      if (extraGenSnapshotOptions.isNotEmpty)
        kExtraGenSnapshotOptions: extraGenSnapshotOptions.join(','),
      if (splitDebugInfoPath != null)
        kSplitDebugInfo: splitDebugInfoPath!,
245 246
      kTrackWidgetCreation: trackWidgetCreation.toString(),
      kIconTreeShakerFlag: treeShakeIcons.toString(),
247 248 249 250 251 252 253 254
      if (bundleSkSLPath != null)
        kBundleSkSLPath: bundleSkSLPath!,
      if (codeSizeDirectory != null)
        kCodeSizeDirectory: codeSizeDirectory!,
      if (fileSystemRoots.isNotEmpty)
        kFileSystemRoots: fileSystemRoots.join(','),
      if (fileSystemScheme != null)
        kFileSystemScheme: fileSystemScheme!,
255 256 257 258
      if (buildName != null)
        kBuildName: buildName!,
      if (buildNumber != null)
        kBuildNumber: buildNumber!,
259 260 261
    };
  }

262

263
  /// Convert to a structured string encoded structure appropriate for usage as
264 265
  /// environment variables or to embed in other scripts.
  ///
266
  /// Fields that are `null` are excluded from this configuration.
267
  Map<String, String> toEnvironmentConfig() {
268
    return <String, String>{
269
      if (dartDefines.isNotEmpty)
270
        'DART_DEFINES': encodeDartDefines(dartDefines),
271
      'DART_OBFUSCATION': dartObfuscation.toString(),
272 273
      if (frontendServerStarterPath != null)
        'FRONTEND_SERVER_STARTER_PATH': frontendServerStarterPath!,
274 275 276 277
      if (extraFrontEndOptions.isNotEmpty)
        'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','),
      if (extraGenSnapshotOptions.isNotEmpty)
        'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','),
278
      if (splitDebugInfoPath != null)
279
        'SPLIT_DEBUG_INFO': splitDebugInfoPath!,
280 281
      'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(),
      'TREE_SHAKE_ICONS': treeShakeIcons.toString(),
282
      if (performanceMeasurementFile != null)
283
        'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile!,
284
      if (bundleSkSLPath != null)
285
        'BUNDLE_SKSL_PATH': bundleSkSLPath!,
286
      'PACKAGE_CONFIG': packagesPath,
287
      if (codeSizeDirectory != null)
288
        'CODE_SIZE_DIRECTORY': codeSizeDirectory!,
289 290
      if (flavor != null)
        'FLAVOR': flavor!,
291 292
    };
  }
293 294 295 296 297

  /// 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.
298
    return <String>[
299
      if (dartDefines.isNotEmpty)
300
        '-Pdart-defines=${encodeDartDefines(dartDefines)}',
301
      '-Pdart-obfuscation=$dartObfuscation',
302 303
      if (frontendServerStarterPath != null)
        '-Pfrontend-server-starter-path=$frontendServerStarterPath',
304 305 306 307
      if (extraFrontEndOptions.isNotEmpty)
        '-Pextra-front-end-options=${extraFrontEndOptions.join(',')}',
      if (extraGenSnapshotOptions.isNotEmpty)
        '-Pextra-gen-snapshot-options=${extraGenSnapshotOptions.join(',')}',
308 309
      if (splitDebugInfoPath != null)
        '-Psplit-debug-info=$splitDebugInfoPath',
310 311
      '-Ptrack-widget-creation=$trackWidgetCreation',
      '-Ptree-shake-icons=$treeShakeIcons',
312 313 314 315 316 317
      if (performanceMeasurementFile != null)
        '-Pperformance-measurement-file=$performanceMeasurementFile',
      if (bundleSkSLPath != null)
        '-Pbundle-sksl-path=$bundleSkSLPath',
      if (codeSizeDirectory != null)
        '-Pcode-size-directory=$codeSizeDirectory',
318
      for (final String projectArg in androidProjectArgs)
319
        '-P$projectArg',
320 321
    ];
  }
322 323 324 325 326 327 328 329 330
}

/// 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,
331
      AndroidArch.x86_64,
332 333
    ],
    this.splitPerAbi = false,
334
    this.fastStart = false,
335
    this.multidexEnabled = false,
336
  });
337

338 339 340 341 342 343 344 345 346 347 348 349
  // 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;
350 351 352

  /// Whether to bootstrap an empty application.
  final bool fastStart;
353 354 355

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

358
/// A summary of the compilation strategy used for Dart.
359
enum BuildMode {
360
  /// Built in JIT mode with no optimizations, enabled asserts, and a VM service.
361
  debug,
362

363
  /// Built in AOT mode with some optimizations and a VM service.
364
  profile,
365

366
  /// Built in AOT mode with all optimizations and no VM service.
367
  release,
368

369
  /// Built in JIT mode with all optimizations and no VM service.
370 371 372 373 374 375 376
  jitRelease;

  factory BuildMode.fromCliName(String value) => values.singleWhere(
        (BuildMode element) => element.cliName == value,
        orElse: () =>
            throw ArgumentError('$value is not a supported build mode'),
      );
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391

  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);
392

393
  /// Whether this mode is using the JIT runtime.
394 395 396 397 398
  bool get isJit => jitModes.contains(this);

  /// Whether this mode is using the precompiled runtime.
  bool get isPrecompiled => !isJit;

399
  String get cliName => snakeCase(name);
400 401

  @override
402
  String toString() => cliName;
403 404
}

405 406 407 408 409 410
/// Environment type of the target device.
enum EnvironmentType {
  physical,
  simulator,
}

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

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

495
String getFriendlyModeName(BuildMode mode) {
496
  return snakeCase(mode.cliName).replaceAll('_', ' ');
497 498
}

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

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

509
enum TargetPlatform {
510
  android,
511
  ios,
512
  darwin,
513
  linux_x64,
514
  linux_arm64,
515
  windows_x64,
516 517
  fuchsia_arm64,
  fuchsia_x64,
518
  tester,
519
  web_javascript,
520
  // The arch specific android target platforms are soft-deprecated.
521 522 523 524 525 526
  // 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,
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
  android_x86;

  String get fuchsiaArchForTargetPlatform {
    switch (this) {
      case TargetPlatform.fuchsia_arm64:
        return 'arm64';
      case TargetPlatform.fuchsia_x64:
        return 'x64';
      case TargetPlatform.android:
      case TargetPlatform.android_arm:
      case TargetPlatform.android_arm64:
      case TargetPlatform.android_x64:
      case TargetPlatform.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_x64:
        throw UnsupportedError('Unexpected Fuchsia platform $this');
    }
  }

  String get simpleName {
    switch (this) {
      case TargetPlatform.linux_x64:
      case TargetPlatform.darwin:
      case TargetPlatform.windows_x64:
        return 'x64';
      case TargetPlatform.linux_arm64:
        return 'arm64';
      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:
        throw UnsupportedError('Unexpected target platform $this');
    }
  }
572 573
}

574
/// iOS and macOS target device architecture.
575 576
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
577
enum DarwinArch {
578
  armv7, // Deprecated. Used to display 32-bit unsupported devices.
579
  arm64,
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
  x86_64;

  /// Returns the Dart SDK's name for the specified target architecture.
  ///
  /// When building for Darwin platforms, the tool invokes architecture-specific
  /// variants of `gen_snapshot`, one for each target architecture. The output
  /// instructions are then built into architecture-specific binaries, which are
  /// merged into a universal binary using the `lipo` tool.
  String get dartName {
    return switch (this) {
      DarwinArch.armv7 => 'armv7',
      DarwinArch.arm64 => 'arm64',
      DarwinArch.x86_64 => 'x64'
    };
  }
595 596
}

597
// TODO(zanderso): replace all android TargetPlatform usage with AndroidArch.
598 599 600 601
enum AndroidArch {
  armeabi_v7a,
  arm64_v8a,
  x86,
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
  x86_64;

  String get archName {
    return switch (this) {
      AndroidArch.armeabi_v7a => 'armeabi-v7a',
      AndroidArch.arm64_v8a => 'arm64-v8a',
      AndroidArch.x86_64 => 'x86_64',
      AndroidArch.x86 => 'x86'
    };
  }

  String get platformName {
    return switch (this) {
      AndroidArch.armeabi_v7a => 'android-arm',
      AndroidArch.arm64_v8a => 'android-arm64',
      AndroidArch.x86_64 => 'android-x64',
      AndroidArch.x86 => 'android-x86'
    };
  }
621 622
}

623
/// The default set of iOS device architectures to build for.
624
List<DarwinArch> defaultIOSArchsForEnvironment(
625 626 627 628
  EnvironmentType environmentType,
  Artifacts artifacts,
) {
  // Handle single-arch local engines.
629 630
  final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo;
  if (localEngineInfo != null) {
631
    final String localEngineName = localEngineInfo.localTargetName;
632 633 634 635 636 637 638
    if (localEngineName.contains('_arm64')) {
      return <DarwinArch>[ DarwinArch.arm64 ];
    }
    if (localEngineName.contains('_sim')) {
      return <DarwinArch>[ DarwinArch.x86_64 ];
    }
  } else if (environmentType == EnvironmentType.simulator) {
639 640
    return <DarwinArch>[
      DarwinArch.x86_64,
641
      DarwinArch.arm64,
642
    ];
643
  }
644 645 646
  return <DarwinArch>[
    DarwinArch.arm64,
  ];
647
}
648

649 650 651
/// The default set of macOS device architectures to build for.
List<DarwinArch> defaultMacOSArchsForEnvironment(Artifacts artifacts) {
  // Handle single-arch local engines.
652 653
  final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo;
  if (localEngineInfo != null) {
654
    if (localEngineInfo.localTargetName.contains('_arm64')) {
655 656 657 658 659 660 661 662 663 664
      return <DarwinArch>[ DarwinArch.arm64 ];
    }
    return <DarwinArch>[ DarwinArch.x86_64 ];
  }
  return <DarwinArch>[
    DarwinArch.x86_64,
    DarwinArch.arm64,
  ];
}

665
DarwinArch getIOSArchForName(String arch) {
666 667
  switch (arch) {
    case 'armv7':
668
    case 'armv7f': // iPhone 4S.
669
    case 'armv7s': // iPad 4.
670
      return DarwinArch.armv7;
671
    case 'arm64':
672
    case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices.
673
      return DarwinArch.arm64;
674 675
    case 'x86_64':
      return DarwinArch.x86_64;
676
  }
677
  throw Exception('Unsupported iOS arch name "$arch"');
678 679
}

680 681 682 683 684 685 686 687 688 689
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"');
}

690
String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) {
691 692
  switch (platform) {
    case TargetPlatform.android_arm:
693
      return 'android-arm';
694 695
    case TargetPlatform.android_arm64:
      return 'android-arm64';
696
    case TargetPlatform.android_x64:
697
      return 'android-x64';
698
    case TargetPlatform.android_x86:
699
      return 'android-x86';
700
    case TargetPlatform.ios:
701
      if (darwinArch != null) {
702
        return 'ios-${darwinArch.name}';
703
      }
704
      return 'ios';
705 706
    case TargetPlatform.darwin:
      if (darwinArch != null) {
707
        return 'darwin-${darwinArch.name}';
708 709
      }
      return 'darwin';
710
    case TargetPlatform.linux_x64:
711
      return 'linux-x64';
712
    case TargetPlatform.linux_arm64:
713
      return 'linux-arm64';
714
    case TargetPlatform.windows_x64:
715
      return 'windows-x64';
716 717 718 719
    case TargetPlatform.fuchsia_arm64:
      return 'fuchsia-arm64';
    case TargetPlatform.fuchsia_x64:
      return 'fuchsia-x64';
720 721
    case TargetPlatform.tester:
      return 'flutter-tester';
722 723
    case TargetPlatform.web_javascript:
      return 'web-javascript';
724 725
    case TargetPlatform.android:
      return 'android';
726
  }
727 728
}

729
TargetPlatform getTargetPlatformForName(String platform) {
730
  switch (platform) {
731 732
    case 'android':
      return TargetPlatform.android;
733 734
    case 'android-arm':
      return TargetPlatform.android_arm;
735 736
    case 'android-arm64':
      return TargetPlatform.android_arm64;
737 738 739 740
    case 'android-x64':
      return TargetPlatform.android_x64;
    case 'android-x86':
      return TargetPlatform.android_x86;
741 742 743 744
    case 'fuchsia-arm64':
      return TargetPlatform.fuchsia_arm64;
    case 'fuchsia-x64':
      return TargetPlatform.fuchsia_x64;
745 746
    case 'ios':
      return TargetPlatform.ios;
747 748 749
    case 'darwin':
    // For backward-compatibility and also for Tester, where it must match
    // host platform name (HostPlatform.darwin_x64)
750
    case 'darwin-x64':
751
    case 'darwin-arm64':
752
      return TargetPlatform.darwin;
753
    case 'linux-x64':
754
      return TargetPlatform.linux_x64;
755 756
   case 'linux-arm64':
      return TargetPlatform.linux_arm64;
757
    case 'windows-x64':
758
      return TargetPlatform.windows_x64;
759 760
    case 'web-javascript':
      return TargetPlatform.web_javascript;
761 762
    case 'flutter-tester':
      return TargetPlatform.tester;
763
  }
764
  throw Exception('Unsupported platform name "$platform"');
765 766
}

767 768 769 770 771 772 773 774 775 776 777
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;
  }
778
  throw Exception('Unsupported Android arch name "$platform"');
779 780
}

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

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

  return HostPlatform.linux_x64;
796
}
797

798 799 800 801
FileSystemEntity getWebPlatformBinariesDirectory(Artifacts artifacts, WebRendererMode webRenderer) {
  return artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder);
}

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

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

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

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

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

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

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

843
/// Returns the web build output directory.
844 845
String getWebBuildDirectory([bool isWasm = false]) {
  return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web');
846 847
}

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

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

863 864
/// Returns the Fuchsia build output directory.
String getFuchsiaBuildDirectory() {
865
  return globals.fs.path.join(getBuildDirectory(), 'fuchsia');
866
}
867 868 869 870 871 872

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

873 874 875 876 877 878 879 880 881 882 883 884
/// 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';

/// Whether to enable or disable track widget creation.
const String kTrackWidgetCreation = 'TrackWidgetCreation';

885 886 887
/// If provided, the frontend server will be started in JIT mode from this file.
const String kFrontendServerStarterPath = 'FrontendServerStarterPath';

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
/// 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.
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
925
/// provided, defaults to x86_64 and arm64.
926 927 928 929
///
/// Supported values are x86_64 and arm64.
const String kDarwinArchs = 'DarwinArchs';

930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
/// The define to control what Android architectures are built for.
///
/// This is expected to be a space-delimited list of architectures.
const String kAndroidArchs = 'AndroidArchs';

/// If the current build is `flutter build aar`.
///
/// This is expected to be a boolean.
///
/// If not provided, defaults to false.
const String kIsAndroidLibrary = 'IsAndroidLibrary';

/// The define to control what min Android SDK version is built for.
///
/// This is expected to be int.
///
/// If not provided, defaults to `minSdkVersion` from gradle_utils.dart.
///
/// This is passed in by flutter.groovy's invocation of `flutter assemble`.
///
/// For more info, see:
/// https://developer.android.com/ndk/guides/sdk-versions#minsdkversion
/// https://developer.android.com/ndk/guides/other_build_systems#overview
const String kMinSdkVersion = 'MinSdkVersion';

955 956 957 958 959 960
/// 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';

961 962 963 964 965 966 967 968 969 970 971 972 973 974
/// Whether to enable Native Assets.
///
/// If true, native assets are built and the mapping for native assets lookup
/// at runtime is embedded in the kernel file.
///
/// If false, native assets are not built, and an empty mapping is embedded in
/// the kernel file. Used for targets that trigger kernel builds but
/// are not OS/architecture specific.
///
/// Supported values are 'true' and 'false'.
///
/// Defaults to 'true'.
const String kNativeAssets = 'NativeAssets';

975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
/// 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';
990 991 992 993

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

994 995 996
/// The app flavor to build.
const String kFlavor = 'Flavor';

997 998
/// The define to pass build number
const String kBuildNumber = 'BuildNumber';
999

1000 1001 1002 1003 1004
/// The action Xcode is taking.
///
/// Will be "build" when building and "install" when archiving.
const String kXcodeAction = 'Action';

1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
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
1015
/// user provided commands will still be encoded.
1016 1017 1018
///
/// If the presence of the `/` character ends up being an issue, this can
/// be changed to use base32 instead.
1019
String encodeDartDefines(List<String> defines) {
1020 1021 1022 1023
  return defines.map(_defineEncoder.convert).join(',');
}

List<String> decodeCommaSeparated(Map<String, String> environmentDefines, String key) {
1024
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
1025 1026
    return <String>[];
  }
1027
  return environmentDefines[key]!
1028 1029 1030
    .split(',')
    .cast<String>()
    .toList();
1031 1032 1033
}

/// Dart defines are encoded inside [environmentDefines] as a comma-separated list.
1034
List<String> decodeDartDefines(Map<String, String> environmentDefines, String key) {
1035
  if (!environmentDefines.containsKey(key) || environmentDefines[key]!.isEmpty) {
1036
    return <String>[];
1037
  }
1038
  return environmentDefines[key]!
1039
    .split(',')
1040
    .map<Object>(_defineDecoder.convert)
1041 1042 1043
    .cast<String>()
    .toList();
}
1044 1045 1046 1047 1048

/// The null safety runtime mode the app should be built in.
enum NullSafetyMode {
  sound,
  unsound,
1049
  /// The null safety mode was not detected. Only supported for 'flutter test'.
1050 1051
  autodetect,
}
1052 1053 1054

String _getCurrentHostPlatformArchName() {
  final HostPlatform hostPlatform = getCurrentHostPlatform();
1055
  return hostPlatform.platformName;
1056
}
1057 1058 1059 1060 1061 1062 1063

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