build_info.dart 17.2 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 'base/context.dart';
6
import 'base/utils.dart';
7
import 'globals.dart' as globals;
8

9 10
/// Information about a build to be performed or used.
class BuildInfo {
11 12 13
  const BuildInfo(
    this.mode,
    this.flavor, {
14
    this.trackWidgetCreation = false,
15 16
    this.extraFrontEndOptions,
    this.extraGenSnapshotOptions,
17 18
    this.fileSystemRoots,
    this.fileSystemScheme,
19 20
    this.buildNumber,
    this.buildName,
21
  });
22 23

  final BuildMode mode;
24

25 26 27 28 29 30 31 32
  /// Represents a custom Android product flavor or an Xcode scheme, null for
  /// using the default.
  ///
  /// If not null, the Gradle build task will be `assembleFlavorMode` (e.g.
  /// `assemblePaidRelease`), and the Xcode build configuration will be
  /// Mode-Flavor (e.g. Release-Paid).
  final String flavor;

33 34 35
  final List<String> fileSystemRoots;
  final String fileSystemScheme;

36 37 38
  /// Whether the build should track widget creation locations.
  final bool trackWidgetCreation;

39 40 41 42 43 44
  /// Extra command-line options for front-end.
  final String extraFrontEndOptions;

  /// Extra command-line options for gen_snapshot.
  final String extraGenSnapshotOptions;

45 46 47 48 49
  /// 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.
50
  final String buildNumber;
51 52 53 54 55 56 57

  /// A "x.y.z" string used as the version number shown to users.
  /// For each new version of your app, you will provide a version number to differentiate it from previous versions.
  /// On Android it is used as versionName.
  /// On Xcode builds it is used as CFBundleShortVersionString,
  final String buildName;

58 59
  static const BuildInfo debug = BuildInfo(BuildMode.debug, null);
  static const BuildInfo profile = BuildInfo(BuildMode.profile, null);
60
  static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null);
61
  static const BuildInfo release = BuildInfo(BuildMode.release, null);
62 63 64 65 66 67 68 69

  /// 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.
  ///
70 71
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
72
  bool get isProfile => mode == BuildMode.profile;
73 74 75

  /// Returns whether a release build is requested.
  ///
76 77
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
78
  bool get isRelease => mode == BuildMode.release;
79

80 81 82 83 84 85
  /// Returns whether a JIT release build is requested.
  ///
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
  bool get isJitRelease => mode == BuildMode.jitRelease;

86 87 88 89
  bool get usesAot => isAotBuildMode(mode);
  bool get supportsEmulator => isEmulatorBuildMode(mode);
  bool get supportsSimulator => isEmulatorBuildMode(mode);
  String get modeName => getModeName(mode);
90
  String get friendlyModeName => getFriendlyModeName(mode);
91 92 93 94 95 96 97 98 99
}

/// 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,
100
      AndroidArch.x86_64,
101 102
    ],
    this.splitPerAbi = false,
Emmanuel Garcia's avatar
Emmanuel Garcia committed
103
    this.shrink = false,
104
    this.fastStart = false,
105
  });
106

107 108 109 110 111 112 113 114 115 116
  // 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;

Emmanuel Garcia's avatar
Emmanuel Garcia committed
117 118
  /// Whether to enable code shrinking on release mode.
  final bool shrink;
119

120 121
  /// The target platforms for the build.
  final Iterable<AndroidArch> targetArchs;
122 123 124

  /// Whether to bootstrap an empty application.
  final bool fastStart;
125 126
}

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
/// 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);
177

178
  /// Whether this mode is using the JIT runtime.
179 180 181 182 183 184 185 186 187 188 189
  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;
}
190 191 192

/// Return the name for the build mode, or "any" if null.
String getNameForBuildMode(BuildMode buildMode) {
193
  return buildMode.name;
194 195 196 197
}

/// Returns the [BuildMode] for a particular `name`.
BuildMode getBuildModeForName(String name) {
198
  return BuildMode.fromName(name);
199 200
}

201 202 203 204 205 206 207 208 209
String validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String buildNumber) {
  if (buildNumber == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
      targetPlatform == TargetPlatform.darwin_x64) {
    // 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, '');
210 211 212
    if (tmpBuildNumber.isEmpty) {
      return null;
    }
213 214 215 216 217 218 219 220 221
    final List<String> segments = tmpBuildNumber
        .split('.')
        .where((String segment) => segment.isNotEmpty)
        .toList();
    if (segments.isEmpty) {
      segments.add('0');
    }
    tmpBuildNumber = segments.join('.');
    if (tmpBuildNumber != buildNumber) {
222
      globals.printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
          '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) {
240
      globals.printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
          'See versionCode at https://developer.android.com/studio/publish/versioning');
    }
    return tmpBuildNumberStr;
  }
  return buildNumber;
}

String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String buildName) {
  if (buildName == null) {
    return null;
  }
  if (targetPlatform == TargetPlatform.ios ||
      targetPlatform == TargetPlatform.darwin_x64) {
    // 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, '');
257 258 259
    if (tmpBuildName.isEmpty) {
      return null;
    }
260 261 262 263 264 265 266 267 268
    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) {
269
      globals.printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
270 271 272 273
          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
    }
    return tmpBuildName;
  }
274 275
  if (targetPlatform == TargetPlatform.android ||
      targetPlatform == TargetPlatform.android_arm ||
276 277 278 279 280 281 282 283 284
      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;
}

285
String getModeName(BuildMode mode) => getEnumName(mode);
286

287 288 289 290
String getFriendlyModeName(BuildMode mode) {
  return snakeCase(getModeName(mode)).replaceAll('_', ' ');
}

291 292 293 294 295
// Returns true if the selected build mode uses ahead-of-time compilation.
bool isAotBuildMode(BuildMode mode) {
  return mode == BuildMode.profile || mode == BuildMode.release;
}

296
// Returns true if the given build mode can be used on emulators / simulators.
297
bool isEmulatorBuildMode(BuildMode mode) {
298
  return mode == BuildMode.debug;
299
}
300

301
enum HostPlatform {
302 303
  darwin_x64,
  linux_x64,
304
  windows_x64,
305 306 307
}

String getNameForHostPlatform(HostPlatform platform) {
308 309
  switch (platform) {
    case HostPlatform.darwin_x64:
310
      return 'darwin-x64';
311
    case HostPlatform.linux_x64:
312
      return 'linux-x64';
313 314
    case HostPlatform.windows_x64:
      return 'windows-x64';
315 316
  }
  assert(false);
pq's avatar
pq committed
317
  return null;
318 319 320
}

enum TargetPlatform {
321
  android,
322
  ios,
323
  darwin_x64,
324
  linux_x64,
325
  windows_x64,
326 327
  fuchsia_arm64,
  fuchsia_x64,
328
  tester,
329
  web_javascript,
330 331 332 333 334 335 336 337
  // The arch specific android target platforms are soft-depreacted.
  // 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,
338 339
}

340
/// iOS and macOS target device architecture.
341 342
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
343
enum DarwinArch {
344 345
  armv7,
  arm64,
346
  x86_64,
347 348
}

349
// TODO(jonahwilliams): replace all android TargetPlatform usage with AndroidArch.
350 351 352 353 354 355 356
enum AndroidArch {
  armeabi_v7a,
  arm64_v8a,
  x86,
  x86_64,
}

357
/// The default set of iOS device architectures to build for.
358 359
const List<DarwinArch> defaultIOSArchs = <DarwinArch>[
  DarwinArch.arm64,
360 361
];

362
String getNameForDarwinArch(DarwinArch arch) {
363
  switch (arch) {
364
    case DarwinArch.armv7:
365
      return 'armv7';
366
    case DarwinArch.arm64:
367
      return 'arm64';
368 369
    case DarwinArch.x86_64:
      return 'x86_64';
370 371 372 373 374
  }
  assert(false);
  return null;
}

375
DarwinArch getIOSArchForName(String arch) {
376 377
  switch (arch) {
    case 'armv7':
378
      return DarwinArch.armv7;
379
    case 'arm64':
380
      return DarwinArch.arm64;
381 382 383 384 385
  }
  assert(false);
  return null;
}

386
String getNameForTargetPlatform(TargetPlatform platform) {
387 388
  switch (platform) {
    case TargetPlatform.android_arm:
389
      return 'android-arm';
390 391
    case TargetPlatform.android_arm64:
      return 'android-arm64';
392
    case TargetPlatform.android_x64:
393
      return 'android-x64';
394
    case TargetPlatform.android_x86:
395
      return 'android-x86';
396
    case TargetPlatform.ios:
397
      return 'ios';
398
    case TargetPlatform.darwin_x64:
399
      return 'darwin-x64';
400
    case TargetPlatform.linux_x64:
401
      return 'linux-x64';
402 403
    case TargetPlatform.windows_x64:
      return 'windows-x64';
404 405 406 407
    case TargetPlatform.fuchsia_arm64:
      return 'fuchsia-arm64';
    case TargetPlatform.fuchsia_x64:
      return 'fuchsia-x64';
408 409
    case TargetPlatform.tester:
      return 'flutter-tester';
410 411
    case TargetPlatform.web_javascript:
      return 'web-javascript';
412 413
    case TargetPlatform.android:
      return 'android';
414 415
  }
  assert(false);
pq's avatar
pq committed
416
  return null;
417 418
}

419 420
TargetPlatform getTargetPlatformForName(String platform) {
  switch (platform) {
421 422
    case 'android':
      return TargetPlatform.android;
423 424
    case 'android-arm':
      return TargetPlatform.android_arm;
425 426
    case 'android-arm64':
      return TargetPlatform.android_arm64;
427 428 429 430
    case 'android-x64':
      return TargetPlatform.android_x64;
    case 'android-x86':
      return TargetPlatform.android_x86;
431 432 433 434
    case 'fuchsia-arm64':
      return TargetPlatform.fuchsia_arm64;
    case 'fuchsia-x64':
      return TargetPlatform.fuchsia_x64;
435 436 437 438 439 440
    case 'ios':
      return TargetPlatform.ios;
    case 'darwin-x64':
      return TargetPlatform.darwin_x64;
    case 'linux-x64':
      return TargetPlatform.linux_x64;
441 442
    case 'windows-x64':
      return TargetPlatform.windows_x64;
443 444
    case 'web-javascript':
      return TargetPlatform.web_javascript;
445
  }
pq's avatar
pq committed
446
  assert(platform != null);
447 448 449
  return null;
}

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
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;
  }
  assert(false);
  return null;
}

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';
  }
  assert(false);
  return null;
}

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';
  }
  assert(false);
  return null;
}

495 496 497 498 499 500 501 502 503 504 505 506
String fuchsiaArchForTargetPlatform(TargetPlatform targetPlatform) {
  switch (targetPlatform) {
    case TargetPlatform.fuchsia_arm64:
      return 'arm64';
    case TargetPlatform.fuchsia_x64:
      return 'x64';
    default:
      assert(false);
      return null;
  }
}

507
HostPlatform getCurrentHostPlatform() {
508
  if (globals.platform.isMacOS) {
509
    return HostPlatform.darwin_x64;
510
  }
511
  if (globals.platform.isLinux) {
512
    return HostPlatform.linux_x64;
513
  }
514
  if (globals.platform.isWindows) {
515
    return HostPlatform.windows_x64;
516
  }
517

518
  globals.printError('Unsupported host platform, defaulting to Linux');
519 520

  return HostPlatform.linux_x64;
521
}
522 523 524

/// Returns the top-level build output directory.
String getBuildDirectory() {
525 526
  // TODO(johnmccutchan): Stop calling this function as part of setting
  // up command line argument processing.
527
  if (context == null || globals.config == null) {
528
    return 'build';
529
  }
530

531 532
  final String buildDir = globals.config.getValue('build-dir') as String ?? 'build';
  if (globals.fs.path.isAbsolute(buildDir)) {
533
    throw Exception(
534
        'build-dir config setting in ${globals.config.configPath} must be relative');
535 536 537 538 539 540
  }
  return buildDir;
}

/// Returns the Android build output directory.
String getAndroidBuildDirectory() {
541
  // TODO(cbracken): move to android subdir.
542 543 544 545 546
  return getBuildDirectory();
}

/// Returns the AOT build output directory.
String getAotBuildDirectory() {
547
  return globals.fs.path.join(getBuildDirectory(), 'aot');
548 549 550 551
}

/// Returns the asset build output directory.
String getAssetBuildDirectory() {
552
  return globals.fs.path.join(getBuildDirectory(), 'flutter_assets');
553 554 555 556
}

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

560 561
/// Returns the macOS build output directory.
String getMacOSBuildDirectory() {
562
  return globals.fs.path.join(getBuildDirectory(), 'macos');
563 564
}

565 566
/// Returns the web build output directory.
String getWebBuildDirectory() {
567
  return globals.fs.path.join(getBuildDirectory(), 'web');
568 569
}

570
/// Returns the Linux build output directory.
571
String getLinuxBuildDirectory() {
572
  return globals.fs.path.join(getBuildDirectory(), 'linux');
573 574
}

575 576
/// Returns the Windows build output directory.
String getWindowsBuildDirectory() {
577
  return globals.fs.path.join(getBuildDirectory(), 'windows');
578 579
}

580 581
/// Returns the Fuchsia build output directory.
String getFuchsiaBuildDirectory() {
582
  return globals.fs.path.join(getBuildDirectory(), 'fuchsia');
583
}