build_info.dart 20.3 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 6
import 'package:meta/meta.dart';

7
import 'base/context.dart';
8
import 'base/logger.dart';
9
import 'base/utils.dart';
10
import 'build_system/targets/icon_tree_shaker.dart';
11
import 'globals.dart' as globals;
12

13 14
/// Information about a build to be performed or used.
class BuildInfo {
15 16 17
  const BuildInfo(
    this.mode,
    this.flavor, {
18
    this.trackWidgetCreation = false,
19 20
    this.extraFrontEndOptions,
    this.extraGenSnapshotOptions,
21 22
    this.fileSystemRoots,
    this.fileSystemScheme,
23 24
    this.buildNumber,
    this.buildName,
25
    this.splitDebugInfoPath,
26
    this.dartObfuscation = false,
27
    this.dartDefines = const <String>[],
28
    this.bundleSkSLPath,
29
    this.dartExperiments = const <String>[],
30
    @required this.treeShakeIcons,
31
    this.performanceMeasurementFile,
32
  });
33 34

  final BuildMode mode;
35

36 37 38
  /// Whether the build should subdset icon fonts.
  final bool treeShakeIcons;

39 40 41 42 43 44 45 46
  /// 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;

47 48 49
  final List<String> fileSystemRoots;
  final String fileSystemScheme;

50 51 52
  /// Whether the build should track widget creation locations.
  final bool trackWidgetCreation;

53
  /// Extra command-line options for front-end.
54
  final List<String> extraFrontEndOptions;
55 56

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

59 60 61 62 63
  /// 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.
64
  final String buildNumber;
65 66 67 68 69 70 71

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

72 73 74 75 76
  /// An optional directory path to save debugging information from dwarf stack
  /// traces. If null, stack trace information is not stripped from the
  /// executable.
  final String splitDebugInfoPath;

77 78 79
  /// Whether to apply dart source code obfuscation.
  final bool dartObfuscation;

80 81 82 83 84
  /// An optional path to a JSON containing object SkSL shaders
  ///
  /// Currently this is only supported for Android builds.
  final String bundleSkSLPath;

85 86 87 88 89 90
  /// 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;

91 92 93
  /// A list of Dart experiments.
  final List<String> dartExperiments;

94 95 96 97 98 99 100
  /// The name of a file where flutter assemble will output performance
  /// information in a JSON format.
  ///
  /// This is not considered a build input and will not force assemble to
  /// rerun tasks.
  final String performanceMeasurementFile;

101 102 103 104
  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);
105 106 107 108 109 110 111 112

  /// 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.
  ///
113 114
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
115
  bool get isProfile => mode == BuildMode.profile;
116 117 118

  /// Returns whether a release build is requested.
  ///
119 120
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
121
  bool get isRelease => mode == BuildMode.release;
122

123 124 125 126 127 128
  /// Returns whether a JIT release build is requested.
  ///
  /// Exactly one of [isDebug], [isProfile], [isJitRelease],
  /// or [isRelease] is true.
  bool get isJitRelease => mode == BuildMode.jitRelease;

129 130 131 132
  bool get usesAot => isAotBuildMode(mode);
  bool get supportsEmulator => isEmulatorBuildMode(mode);
  bool get supportsSimulator => isEmulatorBuildMode(mode);
  String get modeName => getModeName(mode);
133
  String get friendlyModeName => getFriendlyModeName(mode);
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

  /// Convert to a structued string encoded structure appropriate for usage as
  /// environment variables or to embed in other scripts.
  ///
  /// Fields that are `null` are excluded from this configration.
  Map<String, String> toEnvironmentConfig() {
    return <String, String>{
      if (dartDefines?.isNotEmpty ?? false)
        'DART_DEFINES': dartDefines.join(','),
      if (dartObfuscation != null)
        'DART_OBFUSCATION': dartObfuscation.toString(),
      if (extraFrontEndOptions?.isNotEmpty ?? false)
        'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions.join(','),
      if (extraGenSnapshotOptions?.isNotEmpty ?? false)
        'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions.join(','),
      if (splitDebugInfoPath != null)
        'SPLIT_DEBUG_INFO': splitDebugInfoPath,
      if (trackWidgetCreation != null)
        'TRACK_WIDGET_CREATION': trackWidgetCreation.toString(),
      if (treeShakeIcons != null)
        'TREE_SHAKE_ICONS': treeShakeIcons.toString(),
155 156
      if (performanceMeasurementFile != null)
        'PERFORMANCE_MEASUREMENT_FILE': performanceMeasurementFile,
157 158
    };
  }
159 160 161 162 163 164 165 166 167
}

/// 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,
168
      AndroidArch.x86_64,
169 170
    ],
    this.splitPerAbi = false,
Emmanuel Garcia's avatar
Emmanuel Garcia committed
171
    this.shrink = false,
172
    this.fastStart = false,
173
  });
174

175 176 177 178 179 180 181 182 183 184
  // 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
185 186
  /// Whether to enable code shrinking on release mode.
  final bool shrink;
187

188 189
  /// The target platforms for the build.
  final Iterable<AndroidArch> targetArchs;
190 191 192

  /// Whether to bootstrap an empty application.
  final bool fastStart;
193 194
}

195 196 197 198 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 234 235 236 237 238 239 240 241 242 243 244
/// 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);
245

246
  /// Whether this mode is using the JIT runtime.
247 248 249 250 251 252 253 254 255 256 257
  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;
}
258 259 260

/// Return the name for the build mode, or "any" if null.
String getNameForBuildMode(BuildMode buildMode) {
261
  return buildMode.name;
262 263 264 265
}

/// Returns the [BuildMode] for a particular `name`.
BuildMode getBuildModeForName(String name) {
266
  return BuildMode.fromName(name);
267 268
}

269
String validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String buildNumber, Logger logger) {
270 271 272 273 274 275 276 277
  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, '');
278 279 280
    if (tmpBuildNumber.isEmpty) {
      return null;
    }
281 282 283 284 285 286 287 288 289
    final List<String> segments = tmpBuildNumber
        .split('.')
        .where((String segment) => segment.isNotEmpty)
        .toList();
    if (segments.isEmpty) {
      segments.add('0');
    }
    tmpBuildNumber = segments.join('.');
    if (tmpBuildNumber != buildNumber) {
290
      logger.printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
          '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) {
308
      logger.printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
309 310 311 312 313 314 315
          'See versionCode at https://developer.android.com/studio/publish/versioning');
    }
    return tmpBuildNumberStr;
  }
  return buildNumber;
}

316
String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String buildName, Logger logger) {
317 318 319 320 321 322 323 324
  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, '');
325 326 327
    if (tmpBuildName.isEmpty) {
      return null;
    }
328 329 330 331 332 333 334 335 336
    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) {
337
      logger.printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
338 339 340 341
          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
    }
    return tmpBuildName;
  }
342 343
  if (targetPlatform == TargetPlatform.android ||
      targetPlatform == TargetPlatform.android_arm ||
344 345 346 347 348 349 350 351 352
      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;
}

353
String getModeName(BuildMode mode) => getEnumName(mode);
354

355 356 357 358
String getFriendlyModeName(BuildMode mode) {
  return snakeCase(getModeName(mode)).replaceAll('_', ' ');
}

359 360 361 362 363
// Returns true if the selected build mode uses ahead-of-time compilation.
bool isAotBuildMode(BuildMode mode) {
  return mode == BuildMode.profile || mode == BuildMode.release;
}

364
// Returns true if the given build mode can be used on emulators / simulators.
365
bool isEmulatorBuildMode(BuildMode mode) {
366
  return mode == BuildMode.debug;
367
}
368

369
enum HostPlatform {
370 371
  darwin_x64,
  linux_x64,
372
  windows_x64,
373 374 375
}

String getNameForHostPlatform(HostPlatform platform) {
376 377
  switch (platform) {
    case HostPlatform.darwin_x64:
378
      return 'darwin-x64';
379
    case HostPlatform.linux_x64:
380
      return 'linux-x64';
381 382
    case HostPlatform.windows_x64:
      return 'windows-x64';
383 384
  }
  assert(false);
pq's avatar
pq committed
385
  return null;
386 387 388
}

enum TargetPlatform {
389
  android,
390
  ios,
391
  darwin_x64,
392
  linux_x64,
393
  windows_x64,
394 395
  fuchsia_arm64,
  fuchsia_x64,
396
  tester,
397
  web_javascript,
398 399 400 401 402 403 404 405
  // 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,
406 407
}

408
/// iOS and macOS target device architecture.
409 410
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
411
enum DarwinArch {
412 413
  armv7,
  arm64,
414
  x86_64,
415 416
}

417
// TODO(jonahwilliams): replace all android TargetPlatform usage with AndroidArch.
418 419 420 421 422 423 424
enum AndroidArch {
  armeabi_v7a,
  arm64_v8a,
  x86,
  x86_64,
}

425
/// The default set of iOS device architectures to build for.
426 427
const List<DarwinArch> defaultIOSArchs = <DarwinArch>[
  DarwinArch.arm64,
428 429
];

430
String getNameForDarwinArch(DarwinArch arch) {
431
  switch (arch) {
432
    case DarwinArch.armv7:
433
      return 'armv7';
434
    case DarwinArch.arm64:
435
      return 'arm64';
436 437
    case DarwinArch.x86_64:
      return 'x86_64';
438 439 440 441 442
  }
  assert(false);
  return null;
}

443
DarwinArch getIOSArchForName(String arch) {
444 445
  switch (arch) {
    case 'armv7':
446
    case 'armv7f': // iPhone 4S.
447
    case 'armv7s': // iPad 4.
448
      return DarwinArch.armv7;
449
    case 'arm64':
450
    case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices.
451
      return DarwinArch.arm64;
452 453
    case 'x86_64':
      return DarwinArch.x86_64;
454
  }
455
  throw Exception('Unsupported iOS arch name "$arch"');
456 457
}

458
String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch darwinArch}) {
459 460
  switch (platform) {
    case TargetPlatform.android_arm:
461
      return 'android-arm';
462 463
    case TargetPlatform.android_arm64:
      return 'android-arm64';
464
    case TargetPlatform.android_x64:
465
      return 'android-x64';
466
    case TargetPlatform.android_x86:
467
      return 'android-x86';
468
    case TargetPlatform.ios:
469 470 471
      if (darwinArch != null) {
        return 'ios-${getNameForDarwinArch(darwinArch)}';
      }
472
      return 'ios';
473
    case TargetPlatform.darwin_x64:
474
      return 'darwin-x64';
475
    case TargetPlatform.linux_x64:
476
      return 'linux-x64';
477 478
    case TargetPlatform.windows_x64:
      return 'windows-x64';
479 480 481 482
    case TargetPlatform.fuchsia_arm64:
      return 'fuchsia-arm64';
    case TargetPlatform.fuchsia_x64:
      return 'fuchsia-x64';
483 484
    case TargetPlatform.tester:
      return 'flutter-tester';
485 486
    case TargetPlatform.web_javascript:
      return 'web-javascript';
487 488
    case TargetPlatform.android:
      return 'android';
489 490
  }
  assert(false);
pq's avatar
pq committed
491
  return null;
492 493
}

494 495
TargetPlatform getTargetPlatformForName(String platform) {
  switch (platform) {
496 497
    case 'android':
      return TargetPlatform.android;
498 499
    case 'android-arm':
      return TargetPlatform.android_arm;
500 501
    case 'android-arm64':
      return TargetPlatform.android_arm64;
502 503 504 505
    case 'android-x64':
      return TargetPlatform.android_x64;
    case 'android-x86':
      return TargetPlatform.android_x86;
506 507 508 509
    case 'fuchsia-arm64':
      return TargetPlatform.fuchsia_arm64;
    case 'fuchsia-x64':
      return TargetPlatform.fuchsia_x64;
510 511 512 513 514 515
    case 'ios':
      return TargetPlatform.ios;
    case 'darwin-x64':
      return TargetPlatform.darwin_x64;
    case 'linux-x64':
      return TargetPlatform.linux_x64;
516 517
    case 'windows-x64':
      return TargetPlatform.windows_x64;
518 519
    case 'web-javascript':
      return TargetPlatform.web_javascript;
520
  }
pq's avatar
pq committed
521
  assert(platform != null);
522 523 524
  return null;
}

525 526 527 528 529 530 531 532 533 534 535
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;
  }
536
  throw Exception('Unsupported Android arch name "$platform"');
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
}

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

569 570 571 572 573 574 575 576 577 578 579 580
String fuchsiaArchForTargetPlatform(TargetPlatform targetPlatform) {
  switch (targetPlatform) {
    case TargetPlatform.fuchsia_arm64:
      return 'arm64';
    case TargetPlatform.fuchsia_x64:
      return 'x64';
    default:
      assert(false);
      return null;
  }
}

581
HostPlatform getCurrentHostPlatform() {
582
  if (globals.platform.isMacOS) {
583
    return HostPlatform.darwin_x64;
584
  }
585
  if (globals.platform.isLinux) {
586
    return HostPlatform.linux_x64;
587
  }
588
  if (globals.platform.isWindows) {
589
    return HostPlatform.windows_x64;
590
  }
591

592
  globals.printError('Unsupported host platform, defaulting to Linux');
593 594

  return HostPlatform.linux_x64;
595
}
596 597 598

/// Returns the top-level build output directory.
String getBuildDirectory() {
599 600
  // TODO(johnmccutchan): Stop calling this function as part of setting
  // up command line argument processing.
601
  if (context == null || globals.config == null) {
602
    return 'build';
603
  }
604

605 606
  final String buildDir = globals.config.getValue('build-dir') as String ?? 'build';
  if (globals.fs.path.isAbsolute(buildDir)) {
607
    throw Exception(
608
        'build-dir config setting in ${globals.config.configPath} must be relative');
609 610 611 612 613 614
  }
  return buildDir;
}

/// Returns the Android build output directory.
String getAndroidBuildDirectory() {
615
  // TODO(cbracken): move to android subdir.
616 617 618 619 620
  return getBuildDirectory();
}

/// Returns the AOT build output directory.
String getAotBuildDirectory() {
621
  return globals.fs.path.join(getBuildDirectory(), 'aot');
622 623 624 625
}

/// Returns the asset build output directory.
String getAssetBuildDirectory() {
626
  return globals.fs.path.join(getBuildDirectory(), 'flutter_assets');
627 628 629 630
}

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

634 635
/// Returns the macOS build output directory.
String getMacOSBuildDirectory() {
636
  return globals.fs.path.join(getBuildDirectory(), 'macos');
637 638
}

639 640
/// Returns the web build output directory.
String getWebBuildDirectory() {
641
  return globals.fs.path.join(getBuildDirectory(), 'web');
642 643
}

644
/// Returns the Linux build output directory.
645
String getLinuxBuildDirectory() {
646
  return globals.fs.path.join(getBuildDirectory(), 'linux');
647 648
}

649 650
/// Returns the Windows build output directory.
String getWindowsBuildDirectory() {
651
  return globals.fs.path.join(getBuildDirectory(), 'windows');
652 653
}

654 655
/// Returns the Fuchsia build output directory.
String getFuchsiaBuildDirectory() {
656
  return globals.fs.path.join(getBuildDirectory(), 'fuchsia');
657
}