xcode_backend.sh 11.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1 2
#!/usr/bin/env bash
# Copyright 2014 The Flutter Authors. All rights reserved.
3 4 5
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

6 7 8
# Exit on error
set -e

9
RunCommand() {
xster's avatar
xster committed
10 11 12
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    echo "♦ $*"
  fi
13
  "$@"
14 15 16
  return $?
}

17 18 19 20 21 22 23 24
# When provided with a pipe by the host Flutter build process, output to the
# pipe goes to stdout of the Flutter build process directly.
StreamOutput() {
  if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
    echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
  fi
}

25 26 27 28 29
EchoError() {
  echo "$@" 1>&2
}

AssertExists() {
30 31 32 33 34 35
  if [[ ! -e "$1" ]]; then
    if [[ -h "$1" ]]; then
      EchoError "The path $1 is a symlink to a path that does not exist"
    else
      EchoError "The path $1 does not exist"
    fi
36 37 38 39 40
    exit -1
  fi
  return 0
}

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
ParseFlutterBuildMode() {
  # Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
  # This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
  # they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
  local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"

  case "$build_mode" in
    *release*) build_mode="release";;
    *profile*) build_mode="profile";;
    *debug*) build_mode="debug";;
    *)
      EchoError "========================================================================"
      EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
      EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
      EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
      EchoError "If that is not set, the CONFIGURATION environment variable is used."
      EchoError ""
      EchoError "You can fix this by either adding an appropriately named build"
      EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
      EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
      EchoError "========================================================================"
      exit -1;;
  esac
  echo "${build_mode}"
}

67
BuildApp() {
68 69
  local project_path="${SOURCE_ROOT}/.."
  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
70
    project_path="${FLUTTER_APPLICATION_PATH}"
71 72 73 74
  fi

  local target_path="lib/main.dart"
  if [[ -n "$FLUTTER_TARGET" ]]; then
75
    target_path="${FLUTTER_TARGET}"
76 77
  fi

78 79 80 81 82
  local derived_dir="${SOURCE_ROOT}/Flutter"
  if [[ -e "${project_path}/.ios" ]]; then
    derived_dir="${project_path}/.ios/Flutter"
  fi

83 84
  local bundle_sksl_path=""
  if [[ -n "$BUNDLE_SKSL_PATH" ]]; then
85
    bundle_sksl_path="-dBundleSkSLPath=${BUNDLE_SKSL_PATH}"
86 87
  fi

88 89
  # Default value of assets_path is flutter_assets
  local assets_path="flutter_assets"
90 91 92 93 94 95
  # The value of assets_path can set by add FLTAssetsPath to
  # AppFrameworkInfo.plist.
  if FLTAssetsPath=$(/usr/libexec/PlistBuddy -c "Print :FLTAssetsPath" "${derived_dir}/AppFrameworkInfo.plist" 2>/dev/null); then
    if [[ -n "$FLTAssetsPath" ]]; then
      assets_path="${FLTAssetsPath}"
    fi
96 97
  fi

98 99 100
  # Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
  # This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
  # they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
101
  local build_mode="$(ParseFlutterBuildMode)"
102 103
  local artifact_variant="unknown"
  case "$build_mode" in
104 105 106
    release ) artifact_variant="ios-release";;
    profile ) artifact_variant="ios-profile";;
    debug ) artifact_variant="ios";;
107 108
  esac

109
  # Warn the user if not archiving (ACTION=install) in release mode.
110
  if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
111 112
    echo "warning: Flutter archive not built in Release mode. Ensure FLUTTER_BUILD_MODE \
is set to release or run \"flutter build ios --release\", then re-run Archive from Xcode."
113 114
  fi

115
  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
116
  local flutter_engine_flag=""
117
  local local_engine_flag=""
118
  local flutter_framework="${framework_path}/Flutter.xcframework"
119

120 121 122 123
  if [[ -n "$FLUTTER_ENGINE" ]]; then
    flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
  fi

124
  if [[ -n "$LOCAL_ENGINE" ]]; then
125 126 127 128 129 130 131 132 133 134 135 136
    if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
      EchoError "========================================================================"
      EchoError "ERROR: Requested build with Flutter local engine at '${LOCAL_ENGINE}'"
      EchoError "This engine is not compatible with FLUTTER_BUILD_MODE: '${build_mode}'."
      EchoError "You can fix this by updating the LOCAL_ENGINE environment variable, or"
      EchoError "by running:"
      EchoError "  flutter build ios --local-engine=ios_${build_mode}"
      EchoError "or"
      EchoError "  flutter build ios --local-engine=ios_${build_mode}_unopt"
      EchoError "========================================================================"
      exit -1
    fi
137
    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
138
    flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.xcframework"
139
  fi
140
  local bitcode_flag=""
141
  if [[ "$ENABLE_BITCODE" == "YES" && "$ACTION" == "install" ]]; then
142
    bitcode_flag="true"
143 144
  fi

145
  # TODO(jmagman): use assemble copied engine in add-to-app.
146
  if [[ -e "${project_path}/.ios" ]]; then
147
    RunCommand rsync -av --delete --filter "- .DS_Store" "${flutter_framework}" "${derived_dir}/engine"
148 149 150 151
  fi

  RunCommand pushd "${project_path}" > /dev/null

152 153 154 155 156
  local verbose_flag=""
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    verbose_flag="--verbose"
  fi

157 158 159
  local performance_measurement_option=""
  if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
    performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
160 161
  fi

162 163 164 165 166
  local code_size_directory=""
  if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
    code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
  fi

167 168 169 170 171
  local codesign_identity_flag=""
  if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_REQUIRED:-}" != "NO" ]]; then
    codesign_identity_flag="-dCodesignIdentity=${EXPANDED_CODE_SIGN_IDENTITY}"
  fi

172 173 174 175 176
  RunCommand "${FLUTTER_ROOT}/bin/flutter"                                \
    ${verbose_flag}                                                       \
    ${flutter_engine_flag}                                                \
    ${local_engine_flag}                                                  \
    assemble                                                              \
177
    --no-version-check                                                    \
178
    --output="${BUILT_PRODUCTS_DIR}/"                                     \
179
    ${performance_measurement_option}                                     \
180 181 182 183
    -dTargetPlatform=ios                                                  \
    -dTargetFile="${target_path}"                                         \
    -dBuildMode=${build_mode}                                             \
    -dIosArchs="${ARCHS}"                                                 \
184
    -dSdkRoot="${SDKROOT}"                                                \
185
    -dSplitDebugInfo="${SPLIT_DEBUG_INFO}"                                \
186 187 188
    -dTreeShakeIcons="${TREE_SHAKE_ICONS}"                                \
    -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}"                      \
    -dDartObfuscation="${DART_OBFUSCATION}"                               \
189
    -dEnableBitcode="${bitcode_flag}"                                     \
190
    ${codesign_identity_flag}                                             \
191
    ${bundle_sksl_path}                                                   \
192
    ${code_size_directory}                                                \
193
    --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}"             \
194
    --DartDefines="${DART_DEFINES}"                                       \
195
    --ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}"                   \
196
    "${build_mode}_ios_bundle_flutter_assets"
197 198 199 200 201

  if [[ $? -ne 0 ]]; then
    EchoError "Failed to package ${project_path}."
    exit -1
  fi
202 203
  StreamOutput "done"
  StreamOutput " └─Compiling, linking and signing..."
204

205
  RunCommand popd > /dev/null
206 207 208 209 210

  echo "Project ${project_path} built and packaged successfully."
  return 0
}

211 212 213 214 215
# Adds the App.framework as an embedded binary and the flutter_assets as
# resources.
EmbedFlutterFrameworks() {
  # Embed App.framework from Flutter into the app (after creating the Frameworks directory
  # if it doesn't already exist).
216
  local xcode_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
217
  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
218
  RunCommand rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/App.framework" "${xcode_frameworks_dir}"
219 220 221

  # Embed the actual Flutter.framework that the Flutter app expects to run against,
  # which could be a local build or an arch/type specific build.
222
  RunCommand rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
223 224 225 226 227 228 229 230 231 232 233 234 235 236

  AddObservatoryBonjourService
}

# Add the observatory publisher Bonjour service to the produced app bundle Info.plist.
AddObservatoryBonjourService() {
  local build_mode="$(ParseFlutterBuildMode)"
  # Debug and profile only.
  if [[ "${build_mode}" == "release" ]]; then
    return
  fi
  local built_products_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"

  if [[ ! -f "${built_products_plist}" ]]; then
237 238 239 240
    # Very occasionally Xcode hasn't created an Info.plist when this runs.
    # The file will be present on re-run.
    echo "${INFOPLIST_PATH} does not exist. Skipping _dartobservatory._tcp NSBonjourServices insertion. Try re-building to enable \"flutter attach\"."
    return
241 242 243 244 245 246 247 248 249 250 251 252 253 254
  fi
  # If there are already NSBonjourServices specified by the app (uncommon), insert the observatory service name to the existing list.
  if plutil -extract NSBonjourServices xml1 -o - "${built_products_plist}"; then
    RunCommand plutil -insert NSBonjourServices.0 -string "_dartobservatory._tcp" "${built_products_plist}"
  else
    # Otherwise, add the NSBonjourServices key and observatory service name.
    RunCommand plutil -insert NSBonjourServices -json "[\"_dartobservatory._tcp\"]" "${built_products_plist}"
  fi

  # Don't override the local network description the Flutter app developer specified (uncommon).
  # This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
  if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
    RunCommand plutil -insert NSLocalNetworkUsageDescription -string "Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds." "${built_products_plist}"
  fi
255 256
}

257 258
# Main entry point.
if [[ $# == 0 ]]; then
259 260 261
  # Named entry points were introduced in Flutter v0.0.7.
  EchoError "error: Your Xcode project is incompatible with this version of Flutter. Run \"rm -rf ios/Runner.xcodeproj\" and \"flutter create .\" to regenerate."
  exit -1
262 263 264 265 266
else
  case $1 in
    "build")
      BuildApp ;;
    "thin")
267 268
      # No-op, thinning is handled during the bundle asset assemble build target.
      ;;
269 270
    "embed")
      EmbedFlutterFrameworks ;;
271
    "embed_and_thin")
272 273
      # Thinning is handled during the bundle asset assemble build target, so just embed.
      EmbedFlutterFrameworks ;;
274 275 276
    "test_observatory_bonjour_service")
      # Exposed for integration testing only.
      AddObservatoryBonjourService ;;
277 278
  esac
fi