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