xcode_backend.sh 12 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 41
    exit -1
  fi
  return 0
}

BuildApp() {
42 43
  local project_path="${SOURCE_ROOT}/.."
  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
44
    project_path="${FLUTTER_APPLICATION_PATH}"
45 46 47 48
  fi

  local target_path="lib/main.dart"
  if [[ -n "$FLUTTER_TARGET" ]]; then
49
    target_path="${FLUTTER_TARGET}"
50 51
  fi

52 53 54 55 56 57 58
  local derived_dir="${SOURCE_ROOT}/Flutter"
  if [[ -e "${project_path}/.ios" ]]; then
    derived_dir="${project_path}/.ios/Flutter"
  fi

  # Default value of assets_path is flutter_assets
  local assets_path="flutter_assets"
59 60 61 62 63 64
  # 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
65 66
  fi

67 68 69 70 71 72
  # 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:]")"
  local artifact_variant="unknown"
  case "$build_mode" in
73 74 75
    *release*) build_mode="release"; artifact_variant="ios-release";;
    *profile*) build_mode="profile"; artifact_variant="ios-profile";;
    *debug*) build_mode="debug"; artifact_variant="ios";;
76
    *)
77 78 79
      EchoError "========================================================================"
      EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
      EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
Chris Bracken's avatar
Chris Bracken committed
80
      EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
81 82 83
      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"
84
      EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
85 86 87 88 89
      EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
      EchoError "========================================================================"
      exit -1;;
  esac

90
  # Warn the user if not archiving (ACTION=install) in release mode.
91
  if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
92 93
    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."
94 95
  fi

96
  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
97
  local flutter_engine_flag=""
98
  local local_engine_flag=""
99 100
  local flutter_framework="${framework_path}/Flutter.framework"
  local flutter_podspec="${framework_path}/Flutter.podspec"
101

102 103 104 105
  if [[ -n "$FLUTTER_ENGINE" ]]; then
    flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
  fi

106
  if [[ -n "$LOCAL_ENGINE" ]]; then
107 108 109 110 111 112 113 114 115 116 117 118
    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
119
    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
120 121
    flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.framework"
    flutter_podspec="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.podspec"
122 123
  fi

124 125
  local bitcode_flag=""
  if [[ $ENABLE_BITCODE == "YES" ]]; then
126
    bitcode_flag="true"
127 128
  fi

129 130 131 132 133 134 135 136 137 138 139 140 141 142
  # TODO(jonahwilliams): move engine copying to build system.
  if [[ -e "${project_path}/.ios" ]]; then
    RunCommand rm -rf -- "${derived_dir}/engine"
    mkdir "${derived_dir}/engine"
    RunCommand cp -r -- "${flutter_podspec}" "${derived_dir}/engine"
    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}/engine"
  else
    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
    RunCommand cp -- "${flutter_podspec}" "${derived_dir}"
    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
  fi

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

143 144 145 146 147
  local verbose_flag=""
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    verbose_flag="--verbose"
  fi

148 149 150
  local performance_measurement_option=""
  if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
    performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
151 152
  fi

153 154 155 156 157 158
  RunCommand "${FLUTTER_ROOT}/bin/flutter"                                \
    ${verbose_flag}                                                       \
    ${flutter_engine_flag}                                                \
    ${local_engine_flag}                                                  \
    assemble                                                              \
    --output="${derived_dir}/"                                            \
159
    ${performance_measurement_option}                                     \
160 161 162 163
    -dTargetPlatform=ios                                                  \
    -dTargetFile="${target_path}"                                         \
    -dBuildMode=${build_mode}                                             \
    -dIosArchs="${ARCHS}"                                                 \
164
    -dSplitDebugInfo="${SPLIT_DEBUG_INFO}"                                \
165 166 167
    -dTreeShakeIcons="${TREE_SHAKE_ICONS}"                                \
    -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}"                      \
    -dDartObfuscation="${DART_OBFUSCATION}"                               \
168
    -dEnableBitcode="${bitcode_flag}"                                     \
169
    --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}"             \
170
    --DartDefines="${DART_DEFINES}"                                       \
171
    -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}"                   \
172
    "${build_mode}_ios_bundle_flutter_assets"
173 174 175 176 177

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

181
  RunCommand popd > /dev/null
182 183 184 185 186

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

187 188 189 190 191 192 193 194 195 196 197 198 199 200
# Returns the CFBundleExecutable for the specified framework directory.
GetFrameworkExecutablePath() {
  local framework_dir="$1"

  local plist_path="${framework_dir}/Info.plist"
  local executable="$(defaults read "${plist_path}" CFBundleExecutable)"
  echo "${framework_dir}/${executable}"
}

# Destructively thins the specified executable file to include only the
# specified architectures.
LipoExecutable() {
  local executable="$1"
  shift
201 202
  # Split $@ into an array.
  read -r -a archs <<< "$@"
203 204 205

  # Extract architecture-specific framework executables.
  local all_executables=()
206
  for arch in "${archs[@]}"; do
207
    local output="${executable}_${arch}"
208
    local lipo_info="$(lipo -info "${executable}")"
209 210 211 212 213 214
    if [[ "${lipo_info}" == "Non-fat file:"* ]]; then
      if [[ "${lipo_info}" != *"${arch}" ]]; then
        echo "Non-fat binary ${executable} is not ${arch}. Running lipo -info:"
        echo "${lipo_info}"
        exit 1
      fi
215
    else
216
      if lipo -output "${output}" -extract "${arch}" "${executable}"; then
217 218 219 220 221 222
        all_executables+=("${output}")
      else
        echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
        lipo -info "${executable}"
        exit 1
      fi
223 224 225
    fi
  done

226 227 228 229 230 231 232 233 234
  # Generate a merged binary from the architecture-specific executables.
  # Skip this step for non-fat executables.
  if [[ ${#all_executables[@]} > 0 ]]; then
    local merged="${executable}_merged"
    lipo -output "${merged}" -create "${all_executables[@]}"

    cp -f -- "${merged}" "${executable}" > /dev/null
    rm -f -- "${merged}" "${all_executables[@]}"
  fi
235 236 237 238 239 240 241 242 243
}

# Destructively thins the specified framework to include only the specified
# architectures.
ThinFramework() {
  local framework_dir="$1"
  shift

  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
244
  LipoExecutable "${executable}" "$@"
245 246 247 248 249 250 251
}

ThinAppFrameworks() {
  local app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
  local frameworks_dir="${app_path}/Frameworks"

  [[ -d "$frameworks_dir" ]] || return 0
252
  find "${app_path}" -type d -name "*.framework" | while read framework_dir; do
253 254 255 256
    ThinFramework "$framework_dir" "$ARCHS"
  done
}

257 258 259
# Adds the App.framework as an embedded binary and the flutter_assets as
# resources.
EmbedFlutterFrameworks() {
260 261 262 263
  local project_path="${SOURCE_ROOT}/.."
  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
    project_path="${FLUTTER_APPLICATION_PATH}"
  fi
264 265 266

  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
  # doesn't exist.
267 268
  local flutter_ios_out_folder="${project_path}/.ios/Flutter"
  local flutter_ios_engine_folder="${project_path}/.ios/Flutter/engine"
269
  if [[ ! -d ${flutter_ios_out_folder} ]]; then
270 271
    flutter_ios_out_folder="${project_path}/ios/Flutter"
    flutter_ios_engine_folder="${project_path}/ios/Flutter"
272 273 274 275 276 277
  fi

  AssertExists "${flutter_ios_out_folder}"

  # Embed App.framework from Flutter into the app (after creating the Frameworks directory
  # if it doesn't already exist).
278
  local xcode_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
279
  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
280
  RunCommand rsync -av --delete "${flutter_ios_out_folder}/App.framework" "${xcode_frameworks_dir}"
281 282 283

  # 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.
284 285 286

  # Copy Xcode behavior and don't copy over headers or modules.
  RunCommand rsync -av --delete --filter "- .DS_Store/" --filter "- Headers/" --filter "- Modules/" "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"
287 288
  if [[ "$ACTION" != "install" || "$ENABLE_BITCODE" == "NO" ]]; then
    # Strip bitcode from the destination unless archiving, or if bitcode is disabled entirely.
289 290
    RunCommand "${DT_TOOLCHAIN_DIR}"/usr/bin/bitcode_strip "${flutter_ios_engine_folder}/Flutter.framework/Flutter" -r -o "${xcode_frameworks_dir}/Flutter.framework/Flutter"
  fi
291

292
  # Sign the binaries we moved.
293 294 295
  if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then
    RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/App.framework/App"
    RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
296
  fi
297 298
}

299 300 301 302 303
EmbedAndThinFrameworks() {
  EmbedFlutterFrameworks
  ThinAppFrameworks
}

304 305
# Main entry point.
if [[ $# == 0 ]]; then
306
  # Backwards-compatibility: if no args are provided, build and embed.
307
  BuildApp
308
  EmbedFlutterFrameworks
309 310 311 312 313 314
else
  case $1 in
    "build")
      BuildApp ;;
    "thin")
      ThinAppFrameworks ;;
315 316
    "embed")
      EmbedFlutterFrameworks ;;
317 318
    "embed_and_thin")
      EmbedAndThinFrameworks ;;
319 320
  esac
fi