flutter 之 ios 腳本 xcode-backend.sh 文件分析

今天學(xué)習(xí)flutter UI 寫的太多有點惡心,今天就想學(xué)點別的。本來打算看點源碼的東西,但是一個人的力量太小了。因此就找點簡單的可以看明白的東西學(xué)習(xí)下。因此,特此分析下flutter 配置的ios腳本

#!/bin/bash
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

RunCommand() {
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    echo "? $*"
  fi
  "$@"
  return $?
}

# 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
}

EchoError() {
  echo "$@" 1>&2
}

AssertExists() {
  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
    exit -1
  fi
  return 0
}

BuildApp() {
  local project_path="${SOURCE_ROOT}/.."
  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
    project_path="${FLUTTER_APPLICATION_PATH}"
  fi

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

  # 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
    *release*) build_mode="release"; artifact_variant="ios-release";;
    *profile*) build_mode="profile"; artifact_variant="ios-profile";;
    *debug*) build_mode="debug"; artifact_variant="ios";;
    *)
      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 varaible."
      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 appriate value for FLUTTER_BUILD_MODE to the"
      EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
      EchoError "========================================================================"
      exit -1;;
  esac

  # Archive builds (ACTION=install) should always run in release mode.
  if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
    EchoError "========================================================================"
    EchoError "ERROR: Flutter archive builds must be run in Release mode."
    EchoError ""
    EchoError "To correct, ensure FLUTTER_BUILD_MODE is set to release or run:"
    EchoError "flutter build ios --release"
    EchoError ""
    EchoError "then re-run Archive from Xcode."
    EchoError "========================================================================"
    exit -1
  fi

  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"

  AssertExists "${framework_path}"
  AssertExists "${project_path}"

  local derived_dir="${SOURCE_ROOT}/Flutter"
  if [[ -e "${project_path}/.ios" ]]; then
    derived_dir="${project_path}/.ios/Flutter"
  fi
  RunCommand mkdir -p -- "$derived_dir"
  AssertExists "$derived_dir"

  RunCommand rm -rf -- "${derived_dir}/App.framework"

  local flutter_engine_flag=""
  local local_engine_flag=""
  local flutter_framework="${framework_path}/Flutter.framework"
  local flutter_podspec="${framework_path}/Flutter.podspec"

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

  if [[ -n "$LOCAL_ENGINE" ]]; then
    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
    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
    flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.framework"
    flutter_podspec="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.podspec"
  fi

  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"
    RunCommand find "${derived_dir}/engine/Flutter.framework" -type f -exec chmod a-w "{}" \;
  else
    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
    RunCommand find "${derived_dir}/Flutter.framework" -type f -exec chmod a-w "{}" \;
  fi

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

  AssertExists "${target_path}"

  local verbose_flag=""
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    verbose_flag="--verbose"
  fi

  local build_dir="${FLUTTER_BUILD_DIR:-build}"

  local track_widget_creation_flag=""
  if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
    track_widget_creation_flag="--track-widget-creation"
  fi

  if [[ "${build_mode}" != "debug" ]]; then
    StreamOutput " ├─Building Dart code..."
    # Transform ARCHS to comma-separated list of target architectures.
    local archs="${ARCHS// /,}"
    if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
      EchoError "========================================================================"
      EchoError "ERROR: Flutter does not support running in profile or release mode on"
      EchoError "the Simulator (this build was: '$build_mode')."
      EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
      EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
      EchoError "with the ${CONFIGURATION} build configuration."
      EchoError "========================================================================"
      exit -1
    fi
    RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics           \
      ${verbose_flag}                                                       \
      build aot                                                             \
      --output-dir="${build_dir}/aot"                                       \
      --target-platform=ios                                                 \
      --target="${target_path}"                                             \
      --${build_mode}                                                       \
      --ios-arch="${archs}"                                                 \
      ${flutter_engine_flag}                                                \
      ${local_engine_flag}                                                  \
      ${track_widget_creation_flag}

    if [[ $? -ne 0 ]]; then
      EchoError "Failed to build ${project_path}."
      exit -1
    fi
    StreamOutput "done"

    local app_framework="${build_dir}/aot/App.framework"

    RunCommand cp -r -- "${app_framework}" "${derived_dir}"

    StreamOutput " ├─Generating dSYM file..."
    # Xcode calls `symbols` during app store upload, which uses Spotlight to
    # find dSYM files for embedded frameworks. When it finds the dSYM file for
    # `App.framework` it throws an error, which aborts the app store upload.
    # To avoid this, we place the dSYM files in a folder ending with ".noindex",
    # which hides it from Spotlight, https://github.com/flutter/flutter/issues/22560.
    RunCommand mkdir -p -- "${build_dir}/dSYMs.noindex"
    RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
    if [[ $? -ne 0 ]]; then
      EchoError "Failed to generate debug symbols (dSYM) file for ${app_framework}/App."
      exit -1
    fi
    StreamOutput "done"

    StreamOutput " ├─Stripping debug symbols..."
    RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
    if [[ $? -ne 0 ]]; then
      EchoError "Failed to strip ${derived_dir}/App.framework/App."
      exit -1
    fi
    StreamOutput "done"

  else
    RunCommand mkdir -p -- "${derived_dir}/App.framework"

    # Build stub for all requested architectures.
    local arch_flags=""
    read -r -a archs <<< "$ARCHS"
    for arch in "${archs[@]}"; do
      arch_flags="${arch_flags}-arch $arch "
    done

    RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
        ${arch_flags} \
        -dynamiclib \
        -Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
        -Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
        -install_name '@rpath/App.framework/App' \
        -o "${derived_dir}/App.framework/App" -)"
  fi

  local plistPath="${project_path}/ios/Flutter/AppFrameworkInfo.plist"
  if [[ -e "${project_path}/.ios" ]]; then
    plistPath="${project_path}/.ios/Flutter/AppFrameworkInfo.plist"
  fi

  RunCommand cp -- "$plistPath" "${derived_dir}/App.framework/Info.plist"

  local precompilation_flag=""
  if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then
    precompilation_flag="--precompiled"
  fi

  StreamOutput " ├─Assembling Flutter resources..."
  RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics             \
    ${verbose_flag}                                                         \
    build bundle                                                            \
    --target-platform=ios                                                   \
    --target="${target_path}"                                               \
    --${build_mode}                                                         \
    --depfile="${build_dir}/snapshot_blob.bin.d"                            \
    --asset-dir="${derived_dir}/App.framework/flutter_assets"               \
    ${precompilation_flag}                                                  \
    ${flutter_engine_flag}                                                  \
    ${local_engine_flag}                                                    \
    ${track_widget_creation_flag}

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

  RunCommand popd > /dev/null

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

# 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
  # Split $@ into an array.
  read -r -a archs <<< "$@"

  # Extract architecture-specific framework executables.
  local all_executables=()
  for arch in "${archs[@]}"; do
    local output="${executable}_${arch}"
    local lipo_info="$(lipo -info "${executable}")"
    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
    else
      lipo -output "${output}" -extract "${arch}" "${executable}"
      if [[ $? == 0 ]]; then
        all_executables+=("${output}")
      else
        echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
        lipo -info "${executable}"
        exit 1
      fi
    fi
  done

  # 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
}

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

  local plist_path="${framework_dir}/Info.plist"
  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
  LipoExecutable "${executable}" "$@"
}

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

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

# Adds the App.framework as an embedded binary and the flutter_assets as
# resources.
EmbedFlutterFrameworks() {
  AssertExists "${FLUTTER_APPLICATION_PATH}"

  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
  # doesn't exist.
  local flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter"
  local flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter/engine"
  if [[ ! -d ${flutter_ios_out_folder} ]]; then
    flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
    flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
  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).
  local xcode_frameworks_dir=${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app/Frameworks"
  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
  RunCommand cp -Rv -- "${flutter_ios_out_folder}/App.framework" "${xcode_frameworks_dir}"

  # 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.
  # Remove it first since Xcode might be trying to hold some of these files - this way we're
  # sure to get a clean copy.
  RunCommand rm -rf -- "${xcode_frameworks_dir}/Flutter.framework"
  RunCommand cp -Rv -- "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"

  # Sign the binaries we moved.
  local identity="${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$CODE_SIGN_IDENTITY}"
  if [[ -n "$identity" && "$identity" != "\"\"" ]]; then
    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/App.framework/App"
    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
  fi
}

# Main entry point.

# TODO(cbracken): improve error handling, then enable set -e

if [[ $# == 0 ]]; then
  # Backwards-compatibility: if no args are provided, build.
  BuildApp
else
  case $1 in
    "build")
      BuildApp ;;
    "thin")
      ThinAppFrameworks ;;
    "embed")
      EmbedFlutterFrameworks ;;
  esac
fi

配置該腳本有兩個命令

/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" thin
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build

分段分析

第一段

RunCommand() {
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    echo "? $*"
  fi
  "$@"
  return $?
}
  • $* 傳遞給腳本或函數(shù)的所有參數(shù)。
  • $@ 傳遞給腳本或函數(shù)的所有參數(shù)。被雙引號(" ")包含時,與 $* 稍有不同,下面將會講到
  • $? 上個命令的退出狀態(tài),或函數(shù)的返回值。
$* 和 $@ 都表示傳遞給函數(shù)或腳本的所有參數(shù),不被雙引號(" ")包含時,都以"$1" "$2" … "$n" 的形式輸出所有參數(shù)。

但是當(dāng)它們被雙引號(" ")包含時,"$*" 會將所有的參數(shù)作為一個整體,以"$1 $2 … $n"的形式輸出所有參數(shù);"$@" 會將各個參數(shù)分開,以"$1" "$2" … "$n" 的形式輸出所有參數(shù)。

$? 可以獲取上一個命令的退出狀態(tài)。所謂退出狀態(tài),就是上一個命令執(zhí)行后的返回結(jié)果。
退出狀態(tài)是一個數(shù)字,一般情況下,大部分命令執(zhí)行成功會返回 0,失敗返回 1。

 -n 判斷變量的值,是否為空  ,變量的值,為空,返回1,為false,變量的值,非空,返回0,為true

這是一個shell腳本函數(shù)的定義

  1. 判斷是否設(shè)置了全局變量VERBOSE_SCRIPT_LOGGING(該變量用來打印日志的)
  2. 設(shè)置了全局變量,那么就打印出傳入該函數(shù)的命令
  3. 執(zhí)行該命令
  4. 返回執(zhí)行上述命令的返回值

這里,我在該函數(shù)中加入了一行代碼

RunCommand() {
  VERBOSE_SCRIPT_LOGGING="1.0.0"
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    echo "? $*"
  fi
  "$@"
  return $?
}

控制臺輸出結(jié)果


image.png

第二段

StreamOutput() {
  if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
    echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
  fi
}

  • > 寫入文件

這個函數(shù)目的

判斷是否配置全局變量SCRIPT_OUTPUT_STREAM_FILE(文件輸出路徑),要是配置了,那么就將第二個參數(shù)$1 寫入該文件中

這里我配置了下本地變量

StreamOutput() {
    SCRIPT_OUTPUT_STREAM_FILE="test.txt"
  if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
    echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
  fi
}

該文件打印結(jié)果是


第三段

EchoError() {
  echo "$@" 1>&2
}
shell上:
0表示標(biāo)準(zhǔn)輸入
1表示標(biāo)準(zhǔn)輸出
2表示標(biāo)準(zhǔn)錯誤輸出
> 默認(rèn)為標(biāo)準(zhǔn)輸出重定向,與 1> 相同
2>&1 意思是把 標(biāo)準(zhǔn)錯誤輸出 重定向到 標(biāo)準(zhǔn)輸出.
&>file 意思是把 標(biāo)準(zhǔn)輸出 和 標(biāo)準(zhǔn)錯誤輸出 都重定向到文件file中
  • 1>&2 將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)錯誤輸出

該函數(shù)根據(jù)上面知識

就是打印出變量 已標(biāo)準(zhǔn)輸出格式輸出

第四段

AssertExists() {
  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
    exit -1
  fi
  return 0
}
  • -e filename 如果 filename存在,則為真
  • -h filename 如果filename存在且是一個連接(快捷方式),則為真

這個函數(shù)就明白了含義

判斷$1 文件存不存在,不存在返回 -1 存在返回 0

第五段

這里出現(xiàn)一個很大的shell 函數(shù) BuildApp(),有兩百行,因此這里我們將其拆分成小段講解

BuildApp(){
....
}

5.1

 local project_path="${SOURCE_ROOT}/.."
 if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
   project_path="${FLUTTER_APPLICATION_PATH}"
 fi
  • local 就是聲明一個變量
    我修改該地方為
  local project_path="${SOURCE_ROOT}/.."
    echo "project_path"
    echo "$project_path"
    echo "$FLUTTER_APPLICATION_PATH"
  if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
    project_path="${FLUTTER_APPLICATION_PATH}"
  fi

輸出結(jié)果



因此我們知道工程是配置了FLUTTER_APPLICATION_PATH路徑的了

該地方就是獲取下工程路徑而已。

5.2

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

這里和上面的方式一樣簡單,略過,打印結(jié)果


5.3

 # 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
    *release*) build_mode="release"; artifact_variant="ios-release";;
    *profile*) build_mode="profile"; artifact_variant="ios-profile";;
    *debug*) build_mode="debug"; artifact_variant="ios";;
    *)
      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 varaible."
      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 appriate value for FLUTTER_BUILD_MODE to the"
      EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
      EchoError "========================================================================"
      exit -1;;
  esac

知識點

  • 在bash中,$( )與 (反引號)都是用來作命令替換的。

  • 命令替換與變量替換差不多,都是用來重組命令行的,先完成引號里的命令行,然后將其結(jié)果替換出來,再重組成新的命令行。

  • ${ }變量替換

  • tr 用來從標(biāo)準(zhǔn)輸入中通過替換或刪除操作進行字符轉(zhuǎn)換.tr主要用于刪除文件中控制字符或進行字符轉(zhuǎn)換.使用tr時要轉(zhuǎn)換兩個字符串:字符串1用于查詢,字符串2用于處理各種轉(zhuǎn)換.tr剛執(zhí)行時,字符串1中的字符被映射到字符串2中的字符,然后轉(zhuǎn)換操作開始.

  • tr的一般格式:tr -c -d -s ["string1_to_translate_from"] ["string2_to_triampsulata_te_to"]

  • |tr "[:upper:]" "[:lower:] 意思就是將大寫轉(zhuǎn)換成小寫字符

  • ${file:-my.file.txt} :假如 $file 沒有設(shè)定或為空值,則使用 my.file.txt 作傳回值。 (非空值時不作處理)

因此,build_mode 這里就能看懂了

1.獲取變量FLUTTER_BUILD_MODE的值,沒有就獲取{CONFIGURATION}的值

  1. 將獲取到的值轉(zhuǎn)換成小寫賦值給build_mode 變量

對該變量進行測試

echo "CONFIGURATION"
echo "$CONFIGURATION"
echo "FLUTTER_BUILD_MODE begin"
echo "$FLUTTER_BUILD_MODE"
echo "FLUTTER_BUILD_MODE end"

echo "${FLUTTER_BUILD_MODE:-Debug}"
  local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
    echo "build_mode"
    echo "$build_mode"

輸出結(jié)果


image.png
  • case是多分支語句判斷
  • "release" 字符串里面的* 是通配符,代表只要字符串里面包含realease 的字符串

case語句的意思是

要是我們獲取的build_mode變量中包含 release ,那么就設(shè)置build_mode 的值是release,而變量artifact_variant 設(shè)置成ios-release.同理 debug 和 profile。要是不是上述三種就報錯退出。

5.4

 # Archive builds (ACTION=install) should always run in release mode.
  if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
    EchoError "========================================================================"
    EchoError "ERROR: Flutter archive builds must be run in Release mode."
    EchoError ""
    EchoError "To correct, ensure FLUTTER_BUILD_MODE is set to release or run:"
    EchoError "flutter build ios --release"
    EchoError ""
    EchoError "then re-run Archive from Xcode."
    EchoError "========================================================================"
    exit -1
  fi

這里判斷要是打包的話,achieve必須使用release版本。


image.png

5.5

 local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"

  AssertExists "${framework_path}"
  AssertExists "${project_path}"

  local derived_dir="${SOURCE_ROOT}/Flutter"
  if [[ -e "${project_path}/.ios" ]]; then
    derived_dir="${project_path}/.ios/Flutter"
  fi
  RunCommand mkdir -p -- "$derived_dir"
  AssertExists "$derived_dir"

  RunCommand rm -rf -- "${derived_dir}/App.framework"

  local flutter_engine_flag=""
  local local_engine_flag=""
  local flutter_framework="${framework_path}/Flutter.framework"
  local flutter_podspec="${framework_path}/Flutter.podspec"

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

shell知識點

  • -e filename 如果 filename存在,則為真
  • mkdir -p -p, --parents 需要時創(chuàng)建上層目錄,如目錄早已存在則不當(dāng)作錯誤

該段代碼分析

1.獲取framework_path的路徑
2.驗證下 framework_path 是否存在
3.驗證下project_path 路徑是否存在
4.設(shè)置derived_dir默認(rèn)配置路徑
5.要是${project_path}/.ios存在, derived_dir 使用配置的路徑
6創(chuàng)建derived_dir路徑
7.驗證derived_dir是否存在
8.刪除derived_dir下面的App.framework
9.生成幾個本地變量
10.配置了FLUTTER_ENGINE變量就設(shè)置flutter_engine_flag 參數(shù)路徑。默認(rèn)是沒有設(shè)置FLUTTER_ENGINE變量的

5.6

 if [[ -n "$LOCAL_ENGINE" ]]; then
    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
    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
    flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.framework"
    flutter_podspec="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.podspec"
  fi

這部分就是判斷是否工程配置了LOCAL_ENGINE變量,配置了LOCAL_ENGINE變量,LOCAL_ENGINE的值中必須包含build_mode。

要是配置了LOCAL_ENGINE,那么需要重新配置local_engine_flag 和flutter_framework 以及flutter_podspec 參數(shù)

5.7

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"
    RunCommand find "${derived_dir}/engine/Flutter.framework" -type f -exec chmod a-w "{}" \;
  else
    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
    RunCommand find "${derived_dir}/Flutter.framework" -type f -exec chmod a-w "{}" \;
  fi

知識點

  • find命令 -type 查找某一類型的文件 后面根文件類型 f代表普通文件
  • find 命令 -exec: find命令對匹配的文件執(zhí)行該參數(shù)所給出的shell命令。相應(yīng)命令的形式為'command' { } ;,注意{ }和\;之間的空格。
  • chmod a-w 取消目錄下的所有文件可寫權(quán)限 。
  1. 判斷{project_path}/.ios 路徑存在,我們需要刪除{project_path}/.ios/Flutter的engine文件夾,重新創(chuàng)建
    2.copy Flutter.podspec 進入engine文件夾
    3.copy Flutter.framework 進入engine文件夾
    4.要是路徑不存在。
    5.刪除Flutter.framework ,重新復(fù)制一份flutter進入
    6.設(shè)置Flutter.framework 權(quán)限.改路徑下的文件沒有寫的權(quán)限

5.8

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

  AssertExists "${target_path}"

  local verbose_flag=""
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    verbose_flag="--verbose"
  fi

  local build_dir="${FLUTTER_BUILD_DIR:-build}"

  local track_widget_creation_flag=""
  if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
    track_widget_creation_flag="--track-widget-creation"
  fi

知識點

  • /dev/null 這條命令的作用是將標(biāo)準(zhǔn)輸出1重定向到/dev/null中。/dev/null代表linux的空設(shè)備文件,所有往這個文件里面寫入的內(nèi)容都會丟失,俗稱“黑洞”。那么執(zhí)行了>/dev/null之后,標(biāo)準(zhǔn)輸出就會不再存在,沒有任何地方能夠找到輸出的內(nèi)容。

image.png

Pushd是Windows操作系統(tǒng)cmd下的一個命令,作用是保存當(dāng)前目錄以供 POPD 命令使用,然后改到指定的目錄。

這段代碼的意思

1.保存project_path路徑,壓棧
2.判斷target_path 是否存在
3 判斷是否需要打印日志(VERBOSE_SCRIPT_LOGGING),需要就設(shè)置變量verbose_flag參數(shù)

  1. 要是FLUTTER_BUILD_DIR 變量使用該變量,否則使用build作為build_dir的值
    5.設(shè)置TRACK_WIDGET_CREATION變量,track_widget_creation_flag變量就賦值

5.9

 if [[ "${build_mode}" != "debug" ]]; then
    StreamOutput " ├─Building Dart code..."
    # Transform ARCHS to comma-separated list of target architectures.
    local archs="${ARCHS// /,}"
    if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
      EchoError "========================================================================"
      EchoError "ERROR: Flutter does not support running in profile or release mode on"
      EchoError "the Simulator (this build was: '$build_mode')."
      EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
      EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
      EchoError "with the ${CONFIGURATION} build configuration."
      EchoError "========================================================================"
      exit -1
    fi
    RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics           \
      ${verbose_flag}                                                       \
      build aot                                                             \
      --output-dir="${build_dir}/aot"                                       \
      --target-platform=ios                                                 \
      --target="${target_path}"                                             \
      --${build_mode}                                                       \
      --ios-arch="${archs}"                                                 \
      ${flutter_engine_flag}                                                \
      ${local_engine_flag}                                                  \
      ${track_widget_creation_flag}

    if [[ $? -ne 0 ]]; then
      EchoError "Failed to build ${project_path}."
      exit -1
    fi
    StreamOutput "done"

    local app_framework="${build_dir}/aot/App.framework"

    RunCommand cp -r -- "${app_framework}" "${derived_dir}"

    StreamOutput " ├─Generating dSYM file..."
    # Xcode calls `symbols` during app store upload, which uses Spotlight to
    # find dSYM files for embedded frameworks. When it finds the dSYM file for
    # `App.framework` it throws an error, which aborts the app store upload.
    # To avoid this, we place the dSYM files in a folder ending with ".noindex",
    # which hides it from Spotlight, https://github.com/flutter/flutter/issues/22560.
    RunCommand mkdir -p -- "${build_dir}/dSYMs.noindex"
    RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
    if [[ $? -ne 0 ]]; then
      EchoError "Failed to generate debug symbols (dSYM) file for ${app_framework}/App."
      exit -1
    fi
    StreamOutput "done"

    StreamOutput " ├─Stripping debug symbols..."
    RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
    if [[ $? -ne 0 ]]; then
      EchoError "Failed to strip ${derived_dir}/App.framework/App."
      exit -1
    fi
    StreamOutput "done"

  else
    RunCommand mkdir -p -- "${derived_dir}/App.framework"

    # Build stub for all requested architectures.
    local arch_flags=""
    read -r -a archs <<< "$ARCHS"
    for arch in "${archs[@]}"; do
      arch_flags="${arch_flags}-arch $arch "
    done

    RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
        ${arch_flags} \
        -dynamiclib \
        -Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
        -Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
        -install_name '@rpath/App.framework/App' \
        -o "${derived_dir}/App.framework/App" -)"
  fi

知識點

  • ${value//pattern/string} 這是shell的變量替換,進行變量內(nèi)容的替換,把與pattern匹配的部分替換為string的內(nèi)容

  • =~是正在表達(dá)式匹配

  • .*在正則表達(dá)式中表示是指任何字符0個或多個

  • xcrun 命令的使用我是通過在中斷man xcrun 命令查看的


    image.png
  • dsymutil 工具是用來生成dsym文件的,我也是通過man dsymutil來查看該命令的使用


    dsymutil命令的使用

    dsymutil命令的參數(shù)
  • dsymutil -o 后面跟文件命令,代表輸出文件路徑

  • strip 命令也是通過man strip 查看,該命令就是剝掉一些符號信息和調(diào)試信息,使文件變小

image.png
  • strip 命令 -x Remove all local symbols (saving only global symbols).

  • strip 命令 -S Remove the debugging symbol table entries (those created by the -g option to cc(1) and other compilers).

  • -ne 代表不等于
    這段代碼分為 debug 和非debug模式
    先看非debug模式的邏輯

非debug模式

1 輸出文案(就是運行到哪里的標(biāo)記,沒有配置輸出文件就不會起作用)
2 獲取archs 變量,將空格用,分割
3 檢查archs變量是不是 i386 或者x86_64,是就打印錯誤日志,退出
4 控制flutter 命令 (這個命令具體干嘛的,暫時不深究)
5 判斷運行flutter 命令是否成功,不成功就退出
6 輸出flutter編譯成功命令
7 聲明變量app_framework 指向編譯文件輸出文件路徑
8 將編譯輸出文件copy到 derived_dir變量所在路徑下
9 打印下面將聲明dsym文件
10 創(chuàng)建dsym文件夾
11 運行xrun,將生成的framework文件的dsym文件輸出到指定文件夾
12.判斷是否生成dsym文件,沒有打印錯誤日志退出
13 刪除debug沒用的一些符號表
14 判斷是否刪除一些已經(jīng)debug過的符號表

debug 模式

知識點

  • read命令 -p(提示語句) -n(字符個數(shù)) -t(等待時間) -s(不回顯) -a :將內(nèi)容讀入到數(shù)值中
  • <<< 就是將后面的內(nèi)容作為前面命令的標(biāo)準(zhǔn)輸入
  • for arch in "${archs[@]}" 遍歷數(shù)組archs

1 創(chuàng)建文件夾
2 根據(jù)arch變量 設(shè)置arch_flags 參數(shù)
3 控制clang 命令(暫時不知道該命令啥用)
4 獲取plist路徑

  1. 將該plist文件copy 到 app.framework中
  2. 設(shè)置預(yù)編譯參數(shù),當(dāng)目前不是debug 并且不是x86_64 結(jié)構(gòu)體的時候,設(shè)置該參數(shù)
    7 flutter 編譯
    8 判斷flutter命令是否成功。不成功退出
    9 輸出文案,編譯完成

5.10

# 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}"
}

1 獲取下app.framwork 的路徑
2 讀取app.framework 目錄下的plist文件中的CFBundleExecutable 字段
3 輸出

5.11

LipoExecutable() {
  local executable="$1"
  shift
  # Split $@ into an array.
  read -r -a archs <<< "$@"

  # Extract architecture-specific framework executables.
  local all_executables=()
  for arch in "${archs[@]}"; do
    local output="${executable}_${arch}"
    local lipo_info="$(lipo -info "${executable}")"
    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
    else
      lipo -output "${output}" -extract "${arch}" "${executable}"
      if [[ $? == 0 ]]; then
        all_executables+=("${output}")
      else
        echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
        lipo -info "${executable}"
        exit 1
      fi
    fi
  done

  # 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
}

1 獲取執(zhí)行app的路徑
2 獲取archs 參數(shù)
3 輸出文件命名
4 獲取下可執(zhí)行文件的info信息
5 如果 可執(zhí)行文件是 not-fat文件,但是卻不是arch變量里面的及結(jié)構(gòu)體,輸出錯誤 。要是是not fat 并且和arch一樣,那么不做處理(其實就是對not fat文件校驗啦。不做處理)
6 執(zhí)行l(wèi)ipo 命令 將該結(jié)構(gòu)體輸出到執(zhí)行文件

 -extract arch_type
              Take  one  universal input file and copy the arch_type from that
              universal file into a universal output file containing only that
              architecture.
只有一個結(jié)構(gòu)體的 not fat文件

7 執(zhí)行成功,保存輸出文件路徑到all_executables.不成功退出
8 循環(huán)結(jié)束后,讀取all_executables 中的結(jié)構(gòu)體
9 lipo 合并所有的結(jié)構(gòu)體 到merged 變量
10 copy merged 路徑下的文件到可執(zhí)行程序應(yīng)該的位置
11 刪除中間生成的all_executables 中的文件

5.12

ThinFramework() {
  local framework_dir="$1"
  shift

  local plist_path="${framework_dir}/Info.plist"
  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
  LipoExecutable "${executable}" "$@"
}
  • Shell編程中Shift的用法位置參數(shù)可以用shift命令左移。比如shift 3表示原來的4現(xiàn)在變成1,原來的5現(xiàn)在變成2等等,原來的1、2、3丟棄,0不移動

1 獲取第一個參數(shù)
2 參數(shù)左移動
3 獲取下plist文件路徑
4 獲取app執(zhí)行路徑
5 執(zhí)行 LipoExecutable

5.13

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

  [[ -d "$frameworks_dir" ]] || return 0
  find "${app_path}" -type d -name "*.framework" | while read framework_dir; do
    ThinFramework "$framework_dir" "$ARCHS"
  done
}
  • -d filename 如果 filename為目錄,則為真 [ -d /tmp/mydir ]
  • [[ -d "$frameworks_dir" ]] || return 0 意思是要是[[]] 為真就過,否則就返回,判斷frameworks_dir 是不是文件夾
  • find "${app_path}" -type d -name "*.framework" 查找app_path目錄下的framework

1找到target路徑
2找到framework路徑
3 讀取framework文件夾下的framework 執(zhí)行ThinFramework 命令

5.14

EmbedFlutterFrameworks() {
  AssertExists "${FLUTTER_APPLICATION_PATH}"

  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
  # doesn't exist.
  local flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter"
  local flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter/engine"
  if [[ ! -d ${flutter_ios_out_folder} ]]; then
    flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
    flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
  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).
  local xcode_frameworks_dir=${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app/Frameworks"
  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
  RunCommand cp -Rv -- "${flutter_ios_out_folder}/App.framework" "${xcode_frameworks_dir}"

  # 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.
  # Remove it first since Xcode might be trying to hold some of these files - this way we're
  # sure to get a clean copy.
  RunCommand rm -rf -- "${xcode_frameworks_dir}/Flutter.framework"
  RunCommand cp -Rv -- "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"

  # Sign the binaries we moved.
  local identity="${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$CODE_SIGN_IDENTITY}"
  if [[ -n "$identity" && "$identity" != "\"\"" ]]; then
    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/App.framework/App"
    RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
  fi
}

1判斷FLUTTER_APPLICATION_PATH變量是否存在,不存在退出
2 獲取flutter 的輸出文件夾路徑和 engin 文件夾路徑
3 判斷flutter 的輸出路徑是否存在
4 獲取下xcode_frameworks_dir 文件夾
5 創(chuàng)建該文件夾
6.將app.framwork copy到該路徑下
7 刪除 路徑下的Flutter.framework ,重新copy一份進入該文件夾下
8 簽名

該函數(shù)是讓flutter 和app 內(nèi)嵌到app中

5.15

if [[ $# == 0 ]]; then
  # Backwards-compatibility: if no args are provided, build.
  BuildApp
else
  case $1 in
    "build")
      BuildApp ;;
    "thin")
      ThinAppFrameworks ;;
    "embed")
      EmbedFlutterFrameworks ;;
  esac
fi

根據(jù)傳入的參數(shù)不同執(zhí)行不同的命令

到這里我們把shell腳本基本分析完畢。

這里我們看出build 命令就是生成二進制文件
thin命令就是對文件的合并

補充

xcrun clang -x c \
        ${arch_flags} \
        -dynamiclib \
        -Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
        -Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
        -install_name '@rpath/App.framework/App' \
        -o "${derived_dir}/App.framework/App" -)"

這里就要看clang 的參數(shù)了

  • -x <language> Treat subsequent input files as having type language. (這里我們知道編譯使用的c語言編譯)
  • -arch <architecture> Specify the architecture to build for. (代表生成的build文件結(jié)構(gòu)體類型)
  • dynamiclib 連接動態(tài)庫的意思吧(man clang 和clang -help沒找到該命令)
  • -Xlinker <arg> Pass <arg> to the linker
舉例說明
該命令是用來傳遞給鏈接器ld
Xlinker后面跟的參數(shù)第一個是空格
對于傳遞“-assert definitions”命令給ld來說,Xlinker要一下子傳遞兩個參數(shù)需要寫兩次“Xlinker”,比如-Xlinker -assert -Xlinker defintions而不能一下子寫成-Xlinker "-assert definitions"因為鏈接器會認(rèn)為這是一個參數(shù),而不是兩個參數(shù)。如果此時你用的是GNU的linker,通常更簡便的做法就是用option=value的方式,比如-Xlinker -Map -Xlinker output.mp可以簡寫成-Xlinker -Map=output.map。
所以對于rpath來說使用Xlinker可以寫成-Xlinker -rpath -Xlinker <dir>(-Xlinker -rpath=<dir>),對于Wl來說可以寫成-Wl,rpath,<dir>(-Wl,rpath=<dir>)。
  • -rpath: “運行”的時候,去找的目錄。運行的時候,要找 .so 文件,會從這個選項里指定的地方去找。對于交叉編譯,交叉編譯鏈接器需已經(jīng)配置 --with-sysroot 選項才能起作用。也就是說,-rpath指定的路徑會被記錄在生成的可執(zhí)行程序中,用于運行時查找需要加載的動態(tài)庫。-rpath-link 則只用于鏈接時查找。

  • -o 輸出文件路徑
    +-install_name 命令

  • @executable_path, @load_path and @rpath

  • @executable_path 這個變量表示可執(zhí)行程序所在的目錄.

  • @loader_path 這個變量表示每一個被加載的 binary (包括App, dylib, framework,plugin等) 所在的目錄.

  • @rpath 只是一個保存著一個或多個路徑的變量.

gcc選項-xlink
-rpath
OS X 下動態(tài)庫的引用
@executable_path ,@loader_path,@rpath

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容