• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/bash
2# Copyright 2016 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6RunCommand() {
7  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
8    echo "♦ $*"
9  fi
10  "$@"
11  return $?
12}
13
14# When provided with a pipe by the host Flutter build process, output to the
15# pipe goes to stdout of the Flutter build process directly.
16StreamOutput() {
17  if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
18    echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
19  fi
20}
21
22EchoError() {
23  echo "$@" 1>&2
24}
25
26AssertExists() {
27  if [[ ! -e "$1" ]]; then
28    if [[ -h "$1" ]]; then
29      EchoError "The path $1 is a symlink to a path that does not exist"
30    else
31      EchoError "The path $1 does not exist"
32    fi
33    exit -1
34  fi
35  return 0
36}
37
38BuildApp() {
39  local project_path="${SOURCE_ROOT}/.."
40  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
41    project_path="${FLUTTER_APPLICATION_PATH}"
42  fi
43
44  local target_path="lib/main.dart"
45  if [[ -n "$FLUTTER_TARGET" ]]; then
46    target_path="${FLUTTER_TARGET}"
47  fi
48
49  local derived_dir="${SOURCE_ROOT}/Flutter"
50  if [[ -e "${project_path}/.ios" ]]; then
51    derived_dir="${project_path}/.ios/Flutter"
52  fi
53
54  # Default value of assets_path is flutter_assets
55  local assets_path="flutter_assets"
56  # The value of assets_path can set by add FLTAssetsPath to AppFrameworkInfo.plist
57  FLTAssetsPath=$(/usr/libexec/PlistBuddy -c "Print :FLTAssetsPath" "${derived_dir}/AppFrameworkInfo.plist" 2>/dev/null)
58  if [[ -n "$FLTAssetsPath" ]]; then
59    assets_path="${FLTAssetsPath}"
60  fi
61
62  # Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
63  # This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
64  # they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
65  local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
66  local artifact_variant="unknown"
67  case "$build_mode" in
68    *release*) build_mode="release"; artifact_variant="ios-release";;
69    *profile*) build_mode="profile"; artifact_variant="ios-profile";;
70    *debug*) build_mode="debug"; artifact_variant="ios";;
71    *)
72      EchoError "========================================================================"
73      EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
74      EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
75      EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
76      EchoError "If that is not set, the CONFIGURATION environment variable is used."
77      EchoError ""
78      EchoError "You can fix this by either adding an appropriately named build"
79      EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
80      EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
81      EchoError "========================================================================"
82      exit -1;;
83  esac
84
85  # Archive builds (ACTION=install) should always run in release mode.
86  if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
87    EchoError "========================================================================"
88    EchoError "ERROR: Flutter archive builds must be run in Release mode."
89    EchoError ""
90    EchoError "To correct, ensure FLUTTER_BUILD_MODE is set to release or run:"
91    EchoError "flutter build ios --release"
92    EchoError ""
93    EchoError "then re-run Archive from Xcode."
94    EchoError "========================================================================"
95    exit -1
96  fi
97
98  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
99
100  AssertExists "${framework_path}"
101  AssertExists "${project_path}"
102
103  RunCommand mkdir -p -- "$derived_dir"
104  AssertExists "$derived_dir"
105
106  RunCommand rm -rf -- "${derived_dir}/App.framework"
107
108  local flutter_engine_flag=""
109  local local_engine_flag=""
110  local flutter_framework="${framework_path}/Flutter.framework"
111  local flutter_podspec="${framework_path}/Flutter.podspec"
112
113  if [[ -n "$FLUTTER_ENGINE" ]]; then
114    flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
115  fi
116
117  local bitcode_flag=""
118  if [[ -n "$LOCAL_ENGINE" ]]; then
119    if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
120      EchoError "========================================================================"
121      EchoError "ERROR: Requested build with Flutter local engine at '${LOCAL_ENGINE}'"
122      EchoError "This engine is not compatible with FLUTTER_BUILD_MODE: '${build_mode}'."
123      EchoError "You can fix this by updating the LOCAL_ENGINE environment variable, or"
124      EchoError "by running:"
125      EchoError "  flutter build ios --local-engine=ios_${build_mode}"
126      EchoError "or"
127      EchoError "  flutter build ios --local-engine=ios_${build_mode}_unopt"
128      EchoError "========================================================================"
129      exit -1
130    fi
131    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
132    flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.framework"
133    flutter_podspec="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.podspec"
134    if [[ $ENABLE_BITCODE == "YES" ]]; then
135      bitcode_flag="--bitcode"
136    fi
137  fi
138
139  if [[ -e "${project_path}/.ios" ]]; then
140    RunCommand rm -rf -- "${derived_dir}/engine"
141    mkdir "${derived_dir}/engine"
142    RunCommand cp -r -- "${flutter_podspec}" "${derived_dir}/engine"
143    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}/engine"
144    RunCommand find "${derived_dir}/engine/Flutter.framework" -type f -exec chmod a-w "{}" \;
145  else
146    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
147    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
148    RunCommand find "${derived_dir}/Flutter.framework" -type f -exec chmod a-w "{}" \;
149  fi
150
151  RunCommand pushd "${project_path}" > /dev/null
152
153  AssertExists "${target_path}"
154
155  local verbose_flag=""
156  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
157    verbose_flag="--verbose"
158  fi
159
160  local build_dir="${FLUTTER_BUILD_DIR:-build}"
161
162  local track_widget_creation_flag=""
163  if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
164    track_widget_creation_flag="--track-widget-creation"
165  fi
166
167  if [[ "${build_mode}" != "debug" ]]; then
168    StreamOutput " ├─Building Dart code..."
169    # Transform ARCHS to comma-separated list of target architectures.
170    local archs="${ARCHS// /,}"
171    if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
172      EchoError "========================================================================"
173      EchoError "ERROR: Flutter does not support running in profile or release mode on"
174      EchoError "the Simulator (this build was: '$build_mode')."
175      EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
176      EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
177      EchoError "with the ${CONFIGURATION} build configuration."
178      EchoError "========================================================================"
179      exit -1
180    fi
181
182    RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics           \
183      ${verbose_flag}                                                       \
184      build aot                                                             \
185      --output-dir="${build_dir}/aot"                                       \
186      --target-platform=ios                                                 \
187      --target="${target_path}"                                             \
188      --${build_mode}                                                       \
189      --ios-arch="${archs}"                                                 \
190      ${flutter_engine_flag}                                                \
191      ${local_engine_flag}                                                  \
192      ${bitcode_flag}
193
194    if [[ $? -ne 0 ]]; then
195      EchoError "Failed to build ${project_path}."
196      exit -1
197    fi
198    StreamOutput "done"
199
200    local app_framework="${build_dir}/aot/App.framework"
201
202    RunCommand cp -r -- "${app_framework}" "${derived_dir}"
203
204    if [[ "${build_mode}" == "release" ]]; then
205      StreamOutput " ├─Generating dSYM file..."
206      # Xcode calls `symbols` during app store upload, which uses Spotlight to
207      # find dSYM files for embedded frameworks. When it finds the dSYM file for
208      # `App.framework` it throws an error, which aborts the app store upload.
209      # To avoid this, we place the dSYM files in a folder ending with ".noindex",
210      # which hides it from Spotlight, https://github.com/flutter/flutter/issues/22560.
211      RunCommand mkdir -p -- "${build_dir}/dSYMs.noindex"
212      RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
213      if [[ $? -ne 0 ]]; then
214        EchoError "Failed to generate debug symbols (dSYM) file for ${app_framework}/App."
215        exit -1
216      fi
217      StreamOutput "done"
218
219      StreamOutput " ├─Stripping debug symbols..."
220      RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
221      if [[ $? -ne 0 ]]; then
222        EchoError "Failed to strip ${derived_dir}/App.framework/App."
223        exit -1
224      fi
225      StreamOutput "done"
226    fi
227
228  else
229    RunCommand mkdir -p -- "${derived_dir}/App.framework"
230
231    # Build stub for all requested architectures.
232    local arch_flags=""
233    read -r -a archs <<< "$ARCHS"
234    for arch in "${archs[@]}"; do
235      arch_flags="${arch_flags}-arch $arch "
236    done
237
238    RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
239        ${arch_flags} \
240        -dynamiclib \
241        -Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
242        -Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
243        -install_name '@rpath/App.framework/App' \
244        -o "${derived_dir}/App.framework/App" -)"
245  fi
246
247  local plistPath="${project_path}/ios/Flutter/AppFrameworkInfo.plist"
248  if [[ -e "${project_path}/.ios" ]]; then
249    plistPath="${project_path}/.ios/Flutter/AppFrameworkInfo.plist"
250  fi
251
252  RunCommand cp -- "$plistPath" "${derived_dir}/App.framework/Info.plist"
253
254  local precompilation_flag=""
255  if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then
256    precompilation_flag="--precompiled"
257  fi
258
259  StreamOutput " ├─Assembling Flutter resources..."
260  RunCommand "${FLUTTER_ROOT}/bin/flutter"     \
261    ${verbose_flag}                                                         \
262    build bundle                                                            \
263    --target-platform=ios                                                   \
264    --target="${target_path}"                                               \
265    --${build_mode}                                                         \
266    --depfile="${build_dir}/snapshot_blob.bin.d"                            \
267    --asset-dir="${derived_dir}/App.framework/${assets_path}"               \
268    ${precompilation_flag}                                                  \
269    ${flutter_engine_flag}                                                  \
270    ${local_engine_flag}                                                    \
271    ${track_widget_creation_flag}
272
273  if [[ $? -ne 0 ]]; then
274    EchoError "Failed to package ${project_path}."
275    exit -1
276  fi
277  StreamOutput "done"
278  StreamOutput " └─Compiling, linking and signing..."
279
280  RunCommand popd > /dev/null
281
282  echo "Project ${project_path} built and packaged successfully."
283  return 0
284}
285
286# Returns the CFBundleExecutable for the specified framework directory.
287GetFrameworkExecutablePath() {
288  local framework_dir="$1"
289
290  local plist_path="${framework_dir}/Info.plist"
291  local executable="$(defaults read "${plist_path}" CFBundleExecutable)"
292  echo "${framework_dir}/${executable}"
293}
294
295# Destructively thins the specified executable file to include only the
296# specified architectures.
297LipoExecutable() {
298  local executable="$1"
299  shift
300  # Split $@ into an array.
301  read -r -a archs <<< "$@"
302
303  # Extract architecture-specific framework executables.
304  local all_executables=()
305  for arch in "${archs[@]}"; do
306    local output="${executable}_${arch}"
307    local lipo_info="$(lipo -info "${executable}")"
308    if [[ "${lipo_info}" == "Non-fat file:"* ]]; then
309      if [[ "${lipo_info}" != *"${arch}" ]]; then
310        echo "Non-fat binary ${executable} is not ${arch}. Running lipo -info:"
311        echo "${lipo_info}"
312        exit 1
313      fi
314    else
315      lipo -output "${output}" -extract "${arch}" "${executable}"
316      if [[ $? == 0 ]]; then
317        all_executables+=("${output}")
318      else
319        echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
320        lipo -info "${executable}"
321        exit 1
322      fi
323    fi
324  done
325
326  # Generate a merged binary from the architecture-specific executables.
327  # Skip this step for non-fat executables.
328  if [[ ${#all_executables[@]} > 0 ]]; then
329    local merged="${executable}_merged"
330    lipo -output "${merged}" -create "${all_executables[@]}"
331
332    cp -f -- "${merged}" "${executable}" > /dev/null
333    rm -f -- "${merged}" "${all_executables[@]}"
334  fi
335}
336
337# Destructively thins the specified framework to include only the specified
338# architectures.
339ThinFramework() {
340  local framework_dir="$1"
341  shift
342
343  local plist_path="${framework_dir}/Info.plist"
344  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
345  LipoExecutable "${executable}" "$@"
346}
347
348ThinAppFrameworks() {
349  local app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
350  local frameworks_dir="${app_path}/Frameworks"
351
352  [[ -d "$frameworks_dir" ]] || return 0
353  find "${app_path}" -type d -name "*.framework" | while read framework_dir; do
354    ThinFramework "$framework_dir" "$ARCHS"
355  done
356}
357
358# Adds the App.framework as an embedded binary and the flutter_assets as
359# resources.
360EmbedFlutterFrameworks() {
361  AssertExists "${FLUTTER_APPLICATION_PATH}"
362
363  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
364  # doesn't exist.
365  local flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter"
366  local flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter/engine"
367  if [[ ! -d ${flutter_ios_out_folder} ]]; then
368    flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
369    flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
370  fi
371
372  AssertExists "${flutter_ios_out_folder}"
373
374  # Embed App.framework from Flutter into the app (after creating the Frameworks directory
375  # if it doesn't already exist).
376  local xcode_frameworks_dir=${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app/Frameworks"
377  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
378  RunCommand cp -Rv -- "${flutter_ios_out_folder}/App.framework" "${xcode_frameworks_dir}"
379
380  # Embed the actual Flutter.framework that the Flutter app expects to run against,
381  # which could be a local build or an arch/type specific build.
382  # Remove it first since Xcode might be trying to hold some of these files - this way we're
383  # sure to get a clean copy.
384  RunCommand rm -rf -- "${xcode_frameworks_dir}/Flutter.framework"
385  RunCommand cp -Rv -- "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"
386
387  # Sign the binaries we moved.
388  local identity="${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$CODE_SIGN_IDENTITY}"
389  if [[ -n "$identity" && "$identity" != "\"\"" ]]; then
390    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/App.framework/App"
391    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
392  fi
393}
394
395# Main entry point.
396
397# TODO(cbracken): improve error handling, then enable set -e
398
399if [[ $# == 0 ]]; then
400  # Backwards-compatibility: if no args are provided, build.
401  BuildApp
402else
403  case $1 in
404    "build")
405      BuildApp ;;
406    "thin")
407      ThinAppFrameworks ;;
408    "embed")
409      EmbedFlutterFrameworks ;;
410  esac
411fi
412