• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'base/context.dart';
6import 'base/file_system.dart';
7import 'base/platform.dart';
8import 'base/utils.dart';
9import 'globals.dart';
10
11/// Information about a build to be performed or used.
12class BuildInfo {
13  const BuildInfo(
14    this.mode,
15    this.flavor, {
16    this.trackWidgetCreation = false,
17    this.extraFrontEndOptions,
18    this.extraGenSnapshotOptions,
19    this.fileSystemRoots,
20    this.fileSystemScheme,
21    this.buildNumber,
22    this.buildName,
23  });
24
25  final BuildMode mode;
26
27  /// Represents a custom Android product flavor or an Xcode scheme, null for
28  /// using the default.
29  ///
30  /// If not null, the Gradle build task will be `assembleFlavorMode` (e.g.
31  /// `assemblePaidRelease`), and the Xcode build configuration will be
32  /// Mode-Flavor (e.g. Release-Paid).
33  final String flavor;
34
35  final List<String> fileSystemRoots;
36  final String fileSystemScheme;
37
38  /// Whether the build should track widget creation locations.
39  final bool trackWidgetCreation;
40
41  /// Extra command-line options for front-end.
42  final String extraFrontEndOptions;
43
44  /// Extra command-line options for gen_snapshot.
45  final String extraGenSnapshotOptions;
46
47  /// Internal version number (not displayed to users).
48  /// Each build must have a unique number to differentiate it from previous builds.
49  /// It is used to determine whether one build is more recent than another, with higher numbers indicating more recent build.
50  /// On Android it is used as versionCode.
51  /// On Xcode builds it is used as CFBundleVersion.
52  final String buildNumber;
53
54  /// A "x.y.z" string used as the version number shown to users.
55  /// For each new version of your app, you will provide a version number to differentiate it from previous versions.
56  /// On Android it is used as versionName.
57  /// On Xcode builds it is used as CFBundleShortVersionString,
58  final String buildName;
59
60  static const BuildInfo debug = BuildInfo(BuildMode.debug, null);
61  static const BuildInfo profile = BuildInfo(BuildMode.profile, null);
62  static const BuildInfo release = BuildInfo(BuildMode.release, null);
63
64  /// Returns whether a debug build is requested.
65  ///
66  /// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
67  bool get isDebug => mode == BuildMode.debug;
68
69  /// Returns whether a profile build is requested.
70  ///
71  /// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
72  bool get isProfile => mode == BuildMode.profile;
73
74  /// Returns whether a release build is requested.
75  ///
76  /// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
77  bool get isRelease => mode == BuildMode.release;
78
79  bool get usesAot => isAotBuildMode(mode);
80  bool get supportsEmulator => isEmulatorBuildMode(mode);
81  bool get supportsSimulator => isEmulatorBuildMode(mode);
82  String get modeName => getModeName(mode);
83  String get friendlyModeName => getFriendlyModeName(mode);
84}
85
86/// Information about an Android build to be performed or used.
87class AndroidBuildInfo {
88  const AndroidBuildInfo(
89    this.buildInfo, {
90    this.targetArchs = const <AndroidArch>[
91      AndroidArch.armeabi_v7a,
92      AndroidArch.arm64_v8a,
93    ],
94    this.splitPerAbi = false,
95  });
96
97  // The build info containing the mode and flavor.
98  final BuildInfo buildInfo;
99
100  /// Whether to split the shared library per ABI.
101  ///
102  /// When this is false, multiple ABIs will be contained within one primary
103  /// build artifact. When this is true, multiple build artifacts (one per ABI)
104  /// will be produced.
105  final bool splitPerAbi;
106
107  /// The target platforms for the build.
108  final Iterable<AndroidArch> targetArchs;
109}
110
111/// The type of build.
112enum BuildMode {
113  debug,
114  profile,
115  release,
116}
117
118const List<String> _kBuildModes = <String>[
119  'debug',
120  'profile',
121  'release',
122];
123
124/// Return the name for the build mode, or "any" if null.
125String getNameForBuildMode(BuildMode buildMode) {
126  return _kBuildModes[buildMode.index];
127}
128
129/// Returns the [BuildMode] for a particular `name`.
130BuildMode getBuildModeForName(String name) {
131  switch (name) {
132    case 'debug':
133      return BuildMode.debug;
134    case 'profile':
135      return BuildMode.profile;
136    case 'release':
137      return BuildMode.release;
138  }
139  return null;
140}
141
142String validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String buildNumber) {
143  if (buildNumber == null) {
144    return null;
145  }
146  if (targetPlatform == TargetPlatform.ios ||
147      targetPlatform == TargetPlatform.darwin_x64) {
148    // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
149    final RegExp disallowed = RegExp(r'[^\d\.]');
150    String tmpBuildNumber = buildNumber.replaceAll(disallowed, '');
151    final List<String> segments = tmpBuildNumber
152        .split('.')
153        .where((String segment) => segment.isNotEmpty)
154        .toList();
155    if (segments.isEmpty) {
156      segments.add('0');
157    }
158    tmpBuildNumber = segments.join('.');
159    if (tmpBuildNumber != buildNumber) {
160      printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
161          'See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
162    }
163    return tmpBuildNumber;
164  }
165  if (targetPlatform == TargetPlatform.android_arm ||
166      targetPlatform == TargetPlatform.android_arm64 ||
167      targetPlatform == TargetPlatform.android_x64 ||
168      targetPlatform == TargetPlatform.android_x86) {
169    // See versionCode at https://developer.android.com/studio/publish/versioning
170    final RegExp disallowed = RegExp(r'[^\d]');
171    String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, '');
172    int tmpBuildNumberInt = int.tryParse(tmpBuildNumberStr) ?? 0;
173    if (tmpBuildNumberInt < 1) {
174      tmpBuildNumberInt = 1;
175    }
176    tmpBuildNumberStr = tmpBuildNumberInt.toString();
177    if (tmpBuildNumberStr != buildNumber) {
178      printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
179          'See versionCode at https://developer.android.com/studio/publish/versioning');
180    }
181    return tmpBuildNumberStr;
182  }
183  return buildNumber;
184}
185
186String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String buildName) {
187  if (buildName == null) {
188    return null;
189  }
190  if (targetPlatform == TargetPlatform.ios ||
191      targetPlatform == TargetPlatform.darwin_x64) {
192    // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
193    final RegExp disallowed = RegExp(r'[^\d\.]');
194    String tmpBuildName = buildName.replaceAll(disallowed, '');
195    final List<String> segments = tmpBuildName
196        .split('.')
197        .where((String segment) => segment.isNotEmpty)
198        .toList();
199    while (segments.length < 3) {
200      segments.add('0');
201    }
202    tmpBuildName = segments.join('.');
203    if (tmpBuildName != buildName) {
204      printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
205          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
206    }
207    return tmpBuildName;
208  }
209  if (targetPlatform == TargetPlatform.android_arm ||
210      targetPlatform == TargetPlatform.android_arm64 ||
211      targetPlatform == TargetPlatform.android_x64 ||
212      targetPlatform == TargetPlatform.android_x86) {
213    // See versionName at https://developer.android.com/studio/publish/versioning
214    return buildName;
215  }
216  return buildName;
217}
218
219String getModeName(BuildMode mode) => getEnumName(mode);
220
221String getFriendlyModeName(BuildMode mode) {
222  return snakeCase(getModeName(mode)).replaceAll('_', ' ');
223}
224
225// Returns true if the selected build mode uses ahead-of-time compilation.
226bool isAotBuildMode(BuildMode mode) {
227  return mode == BuildMode.profile || mode == BuildMode.release;
228}
229
230// Returns true if the given build mode can be used on emulators / simulators.
231bool isEmulatorBuildMode(BuildMode mode) {
232  return mode == BuildMode.debug;
233}
234
235enum HostPlatform {
236  darwin_x64,
237  linux_x64,
238  windows_x64,
239}
240
241String getNameForHostPlatform(HostPlatform platform) {
242  switch (platform) {
243    case HostPlatform.darwin_x64:
244      return 'darwin-x64';
245    case HostPlatform.linux_x64:
246      return 'linux-x64';
247    case HostPlatform.windows_x64:
248      return 'windows-x64';
249  }
250  assert(false);
251  return null;
252}
253
254enum TargetPlatform {
255  android_arm,
256  android_arm64,
257  android_x64,
258  android_x86,
259  ios,
260  darwin_x64,
261  linux_x64,
262  windows_x64,
263  fuchsia,
264  tester,
265  web_javascript,
266}
267
268/// iOS and macOS target device architecture.
269//
270// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
271enum DarwinArch {
272  armv7,
273  arm64,
274  x86_64,
275}
276
277enum AndroidArch {
278  armeabi_v7a,
279  arm64_v8a,
280  x86,
281  x86_64,
282}
283
284/// The default set of iOS device architectures to build for.
285const List<DarwinArch> defaultIOSArchs = <DarwinArch>[
286  DarwinArch.arm64,
287];
288
289String getNameForDarwinArch(DarwinArch arch) {
290  switch (arch) {
291    case DarwinArch.armv7:
292      return 'armv7';
293    case DarwinArch.arm64:
294      return 'arm64';
295    case DarwinArch.x86_64:
296      return 'x86_64';
297  }
298  assert(false);
299  return null;
300}
301
302DarwinArch getIOSArchForName(String arch) {
303  switch (arch) {
304    case 'armv7':
305      return DarwinArch.armv7;
306    case 'arm64':
307      return DarwinArch.arm64;
308  }
309  assert(false);
310  return null;
311}
312
313String getNameForTargetPlatform(TargetPlatform platform) {
314  switch (platform) {
315    case TargetPlatform.android_arm:
316      return 'android-arm';
317    case TargetPlatform.android_arm64:
318      return 'android-arm64';
319    case TargetPlatform.android_x64:
320      return 'android-x64';
321    case TargetPlatform.android_x86:
322      return 'android-x86';
323    case TargetPlatform.ios:
324      return 'ios';
325    case TargetPlatform.darwin_x64:
326      return 'darwin-x64';
327    case TargetPlatform.linux_x64:
328      return 'linux-x64';
329    case TargetPlatform.windows_x64:
330      return 'windows-x64';
331    case TargetPlatform.fuchsia:
332      return 'fuchsia';
333    case TargetPlatform.tester:
334      return 'flutter-tester';
335    case TargetPlatform.web_javascript:
336      return 'web-javascript';
337  }
338  assert(false);
339  return null;
340}
341
342TargetPlatform getTargetPlatformForName(String platform) {
343  switch (platform) {
344    case 'android-arm':
345      return TargetPlatform.android_arm;
346    case 'android-arm64':
347      return TargetPlatform.android_arm64;
348    case 'android-x64':
349      return TargetPlatform.android_x64;
350    case 'android-x86':
351      return TargetPlatform.android_x86;
352    case 'ios':
353      return TargetPlatform.ios;
354    case 'darwin-x64':
355      return TargetPlatform.darwin_x64;
356    case 'linux-x64':
357      return TargetPlatform.linux_x64;
358    case 'windows-x64':
359      return TargetPlatform.windows_x64;
360    case 'web-javascript':
361      return TargetPlatform.web_javascript;
362  }
363  assert(platform != null);
364  return null;
365}
366
367AndroidArch getAndroidArchForName(String platform) {
368  switch (platform) {
369    case 'android-arm':
370      return AndroidArch.armeabi_v7a;
371    case 'android-arm64':
372      return AndroidArch.arm64_v8a;
373    case 'android-x64':
374      return AndroidArch.x86_64;
375    case 'android-x86':
376      return AndroidArch.x86;
377  }
378  assert(false);
379  return null;
380}
381
382String getNameForAndroidArch(AndroidArch arch) {
383  switch (arch) {
384    case AndroidArch.armeabi_v7a:
385      return 'armeabi-v7a';
386    case AndroidArch.arm64_v8a:
387      return 'arm64-v8a';
388    case AndroidArch.x86_64:
389      return 'x86_64';
390    case AndroidArch.x86:
391      return 'x86';
392  }
393  assert(false);
394  return null;
395}
396
397String getPlatformNameForAndroidArch(AndroidArch arch) {
398  switch (arch) {
399    case AndroidArch.armeabi_v7a:
400      return 'android-arm';
401    case AndroidArch.arm64_v8a:
402      return 'android-arm64';
403    case AndroidArch.x86_64:
404      return 'android-x64';
405    case AndroidArch.x86:
406      return 'android-x86';
407  }
408  assert(false);
409  return null;
410}
411
412HostPlatform getCurrentHostPlatform() {
413  if (platform.isMacOS)
414    return HostPlatform.darwin_x64;
415  if (platform.isLinux)
416    return HostPlatform.linux_x64;
417  if (platform.isWindows)
418    return HostPlatform.windows_x64;
419
420  printError('Unsupported host platform, defaulting to Linux');
421
422  return HostPlatform.linux_x64;
423}
424
425/// Returns the top-level build output directory.
426String getBuildDirectory() {
427  // TODO(johnmccutchan): Stop calling this function as part of setting
428  // up command line argument processing.
429  if (context == null || config == null)
430    return 'build';
431
432  final String buildDir = config.getValue('build-dir') ?? 'build';
433  if (fs.path.isAbsolute(buildDir)) {
434    throw Exception(
435        'build-dir config setting in ${config.configPath} must be relative');
436  }
437  return buildDir;
438}
439
440/// Returns the Android build output directory.
441String getAndroidBuildDirectory() {
442  // TODO(cbracken): move to android subdir.
443  return getBuildDirectory();
444}
445
446/// Returns the AOT build output directory.
447String getAotBuildDirectory() {
448  return fs.path.join(getBuildDirectory(), 'aot');
449}
450
451/// Returns the asset build output directory.
452String getAssetBuildDirectory() {
453  return fs.path.join(getBuildDirectory(), 'flutter_assets');
454}
455
456/// Returns the iOS build output directory.
457String getIosBuildDirectory() {
458  return fs.path.join(getBuildDirectory(), 'ios');
459}
460
461/// Returns the macOS build output directory.
462String getMacOSBuildDirectory() {
463  return fs.path.join(getBuildDirectory(), 'macos');
464}
465
466/// Returns the web build output directory.
467String getWebBuildDirectory() {
468  return fs.path.join(getBuildDirectory(), 'web');
469}
470
471/// Returns the Linux build output directory.
472String getLinuxBuildDirectory() {
473  return fs.path.join(getBuildDirectory(), 'linux');
474}
475
476/// Returns the Windows build output directory.
477String getWindowsBuildDirectory() {
478  return fs.path.join(getBuildDirectory(), 'windows');
479}
480
481/// Returns the Fuchsia build output directory.
482String getFuchsiaBuildDirectory() {
483  return fs.path.join(getBuildDirectory(), 'fuchsia');
484}
485