xcode_build_settings.dart 8.44 KB
Newer Older
1 2 3 4 5
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../artifacts.dart';
6
import '../base/common.dart';
7 8 9 10
import '../base/file_system.dart';
import '../build_info.dart';
import '../cache.dart';
import '../flutter_manifest.dart';
11
import '../globals.dart' as globals;
12 13 14 15 16 17
import '../project.dart';

String flutterMacOSFrameworkDir(BuildMode mode, FileSystem fileSystem,
    Artifacts artifacts) {
  final String flutterMacOSFramework = artifacts.getArtifactPath(
    Artifact.flutterMacOSFramework,
18
    platform: TargetPlatform.darwin,
19 20 21 22 23 24 25 26 27 28 29 30 31 32
    mode: mode,
  );
  return fileSystem.path
      .normalize(fileSystem.path.dirname(flutterMacOSFramework));
}

/// Writes or rewrites Xcode property files with the specified information.
///
/// useMacOSConfig: Optional parameter that controls whether we use the macOS
/// project file instead. Defaults to false.
///
/// targetOverride: Optional parameter, if null or unspecified the default value
/// from xcode_backend.sh is used 'lib/main.dart'.
Future<void> updateGeneratedXcodeProperties({
33 34 35
  required FlutterProject project,
  required BuildInfo buildInfo,
  String? targetOverride,
36
  bool useMacOSConfig = false,
37
  String? buildDirOverride,
38
}) async {
39
  final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
    project: project,
    buildInfo: buildInfo,
    targetOverride: targetOverride,
    useMacOSConfig: useMacOSConfig,
    buildDirOverride: buildDirOverride,
  );

  _updateGeneratedXcodePropertiesFile(
    project: project,
    xcodeBuildSettings: xcodeBuildSettings,
    useMacOSConfig: useMacOSConfig,
  );

  _updateGeneratedEnvironmentVariablesScript(
    project: project,
    xcodeBuildSettings: xcodeBuildSettings,
    useMacOSConfig: useMacOSConfig,
  );
}

/// Generate a xcconfig file to inherit FLUTTER_ build settings
/// for Xcode targets that need them.
/// See [XcodeBasedProject.generatedXcodePropertiesFile].
void _updateGeneratedXcodePropertiesFile({
64 65
  required FlutterProject project,
  required List<String> xcodeBuildSettings,
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  bool useMacOSConfig = false,
}) {
  final StringBuffer localsBuffer = StringBuffer();

  localsBuffer.writeln('// This is a generated file; do not edit or check into version control.');
  xcodeBuildSettings.forEach(localsBuffer.writeln);
  final File generatedXcodePropertiesFile = useMacOSConfig
    ? project.macos.generatedXcodePropertiesFile
    : project.ios.generatedXcodePropertiesFile;

  generatedXcodePropertiesFile.createSync(recursive: true);
  generatedXcodePropertiesFile.writeAsStringSync(localsBuffer.toString());
}

/// Generate a script to export all the FLUTTER_ environment variables needed
/// as flags for Flutter tools.
/// See [XcodeBasedProject.generatedEnvironmentVariableExportScript].
void _updateGeneratedEnvironmentVariablesScript({
84 85
  required FlutterProject project,
  required List<String> xcodeBuildSettings,
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  bool useMacOSConfig = false,
}) {
  final StringBuffer localsBuffer = StringBuffer();

  localsBuffer.writeln('#!/bin/sh');
  localsBuffer.writeln('# This is a generated file; do not edit or check into version control.');
  for (final String line in xcodeBuildSettings) {
    if (!line.contains('[')) { // Exported conditional Xcode build settings do not work.
      localsBuffer.writeln('export "$line"');
    }
  }

  final File generatedModuleBuildPhaseScript = useMacOSConfig
    ? project.macos.generatedEnvironmentVariableExportScript
    : project.ios.generatedEnvironmentVariableExportScript;
  generatedModuleBuildPhaseScript.createSync(recursive: true);
  generatedModuleBuildPhaseScript.writeAsStringSync(localsBuffer.toString());
  globals.os.chmod(generatedModuleBuildPhaseScript, '755');
}

/// Build name parsed and validated from build info and manifest. Used for CFBundleShortVersionString.
107 108 109
String? parsedBuildName({
  required FlutterManifest manifest,
  BuildInfo? buildInfo,
110
}) {
111
  final String? buildNameToParse = buildInfo?.buildName ?? manifest.buildName;
112 113 114 115
  return validatedBuildNameForPlatform(TargetPlatform.ios, buildNameToParse, globals.logger);
}

/// Build number parsed and validated from build info and manifest. Used for CFBundleVersion.
116 117 118
String? parsedBuildNumber({
  required FlutterManifest manifest,
  BuildInfo? buildInfo,
119
}) {
120 121
  String? buildNumberToParse = buildInfo?.buildNumber ?? manifest.buildNumber;
  final String? buildNumber = validatedBuildNumberForPlatform(
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    TargetPlatform.ios,
    buildNumberToParse,
    globals.logger,
  );
  if (buildNumber != null && buildNumber.isNotEmpty) {
    return buildNumber;
  }
  // Drop back to parsing build name if build number is not present. Build number is optional in the manifest, but
  // FLUTTER_BUILD_NUMBER is required as the backing value for the required CFBundleVersion.
  buildNumberToParse = buildInfo?.buildName ?? manifest.buildName;
  return validatedBuildNumberForPlatform(
    TargetPlatform.ios,
    buildNumberToParse,
    globals.logger,
  );
}

/// List of lines of build settings. Example: 'FLUTTER_BUILD_DIR=build'
140
Future<List<String>> _xcodeBuildSettingsLines({
141 142 143
  required FlutterProject project,
  required BuildInfo buildInfo,
  String? targetOverride,
144
  bool useMacOSConfig = false,
145
  String? buildDirOverride,
146
}) async {
147 148
  final List<String> xcodeBuildSettings = <String>[];

149
  final String flutterRoot = globals.fs.path.normalize(Cache.flutterRoot!);
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
  xcodeBuildSettings.add('FLUTTER_ROOT=$flutterRoot');

  // This holds because requiresProjectRoot is true for this command
  xcodeBuildSettings.add('FLUTTER_APPLICATION_PATH=${globals.fs.path.normalize(project.directory.path)}');

  // Tell CocoaPods behavior to codesign in parallel with rest of scripts to speed it up.
  // Value must be "true", not "YES". https://github.com/CocoaPods/CocoaPods/pull/6088
  xcodeBuildSettings.add('COCOAPODS_PARALLEL_CODE_SIGN=true');

  // Relative to FLUTTER_APPLICATION_PATH, which is [Directory.current].
  if (targetOverride != null) {
    xcodeBuildSettings.add('FLUTTER_TARGET=$targetOverride');
  }

  // The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
  xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');

  final String buildName = parsedBuildName(manifest: project.manifest, buildInfo: buildInfo) ?? '1.0.0';
  xcodeBuildSettings.add('FLUTTER_BUILD_NAME=$buildName');

  final String buildNumber = parsedBuildNumber(manifest: project.manifest, buildInfo: buildInfo) ?? '1';
  xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');

173 174 175
  final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo;
  if (localEngineInfo != null) {
    final String engineOutPath = localEngineInfo.engineOutPath;
176 177
    xcodeBuildSettings.add('FLUTTER_ENGINE=${globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath))}');

178
    final String localEngineName = localEngineInfo.localEngineName;
179 180 181 182 183 184 185 186
    xcodeBuildSettings.add('LOCAL_ENGINE=$localEngineName');

    // Tell Xcode not to build universal binaries for local engines, which are
    // single-architecture.
    //
    // NOTE: this assumes that local engine binary paths are consistent with
    // the conventions uses in the engine: 32-bit iOS engines are built to
    // paths ending in _arm, 64-bit builds are not.
187 188 189 190 191 192 193 194 195

    String arch;
    if (useMacOSConfig) {
      if (localEngineName.contains('_arm64')) {
        arch = 'arm64';
      } else {
        arch = 'x86_64';
      }
    } else {
196
      if (localEngineName.endsWith('_arm')) {
197
        throwToolExit('32-bit iOS local engine binaries are not supported.');
198 199
      } else if (localEngineName.contains('_arm64')) {
        arch = 'arm64';
200 201 202 203 204 205
      } else if (localEngineName.contains('_sim')) {
        arch = 'x86_64';
      } else {
        arch = 'arm64';
      }
    }
206
    xcodeBuildSettings.add('ARCHS=$arch');
207
  }
208

209
  if (!useMacOSConfig) {
210 211 212
    // If any plugins or their dependencies do not support arm64 simulators
    // (to run natively without Rosetta translation on an ARM Mac),
    // the app will fail to build unless it also excludes arm64 simulators.
213
    String excludedSimulatorArchs = 'i386';
214
    if (!(await project.ios.pluginsSupportArmSimulator())) {
215 216 217
      excludedSimulatorArchs += ' arm64';
    }
    xcodeBuildSettings.add('EXCLUDED_ARCHS[sdk=iphonesimulator*]=$excludedSimulatorArchs');
218
    xcodeBuildSettings.add('EXCLUDED_ARCHS[sdk=iphoneos*]=armv7');
219 220 221 222 223 224 225
  }

  for (final MapEntry<String, String> config in buildInfo.toEnvironmentConfig().entries) {
    xcodeBuildSettings.add('${config.key}=${config.value}');
  }
  return xcodeBuildSettings;
}