xcode_backend.sh源码解析

xcode_backend.sh位于$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh
打开Runner.xcworkspace,在Target->Build Phases的Run Script和Thin Binary可看到,先后执行了xcode_backend.sh build和embed_and_thin

主函数入口

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

不带参数就执行build和embed命令,带参数就执行相应的命令。

$# #传递给脚本或函数的参数个数。
$0 #当前脚本的文件名
$n #传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
$* #传递给脚本或函数的所有参数。整体输出
$@ #传递给脚本或函数的所有参数。间隔输出
$? #执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)

主要的4个方法

1、BuildApp
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

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

  local bundle_sksl_path=""
  if [[ -n "$BUNDLE_SKSL_PATH" ]]; then
    bundle_sksl_path="-iBundleSkSLPath=${BUNDLE_SKSL_PATH}"
  fi

  # Default value of assets_path is flutter_assets
  local assets_path="flutter_assets"
  # 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
  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 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

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

  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
  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

  local bitcode_flag=""
  if [[ $ENABLE_BITCODE == "YES" ]]; then
    bitcode_flag="true"
  fi

  # 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

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

  local performance_measurement_option=""
  if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
    performance_measurement_option="--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}"
  fi

  RunCommand "${FLUTTER_ROOT}/bin/flutter"                                \
    ${verbose_flag}                                                       \
    ${flutter_engine_flag}                                                \
    ${local_engine_flag}                                                  \
    assemble                                                              \
    --output="${derived_dir}/"                                            \
    ${performance_measurement_option}                                     \
    -dTargetPlatform=ios                                                  \
    -dTargetFile="${target_path}"                                         \
    -dBuildMode=${build_mode}                                             \
    -dIosArchs="${ARCHS}"                                                 \
    -dSplitDebugInfo="${SPLIT_DEBUG_INFO}"                                \
    -dTreeShakeIcons="${TREE_SHAKE_ICONS}"                                \
    -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}"                      \
    -dDartObfuscation="${DART_OBFUSCATION}"                               \
    -dEnableBitcode="${bitcode_flag}"                                     \
    ${bundle_sksl_path}                                                   \
    --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}"             \
    --DartDefines="${DART_DEFINES}"                                       \
    --ExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}"                   \
    "${build_mode}_ios_bundle_flutter_assets"

  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
}

1、local:局部变量的定义,多使用在函数内部,全局变量为global
2、-n 非空为true,-e 存在为true
3、FLTAssetsPath。在.ios/Flutter/AppFrameworkInfo.plist文件里面查找key为FLTAssetsPath的value,并返回。
shell不能直接解析plist文件,需要借助Mac os的PlistBuddy,未配置环境变量,要用绝对路径调用该命令。注:可以在plist中写入FLTAssetsPath的值来指定assets_path
4、FLUTTER_BUILD_MODE。$( ) 与 ` (反引号)都是用来执行一条命令。| 运算符,管道符号,是unix一个很强大的功能,符号为一条竖线:"|"。用法:command 1 | command 2他的功能是把第一个命令command 1执行的结果作为command2的输入传给command 2。tr把大写转化为小写。注:如果FLUTTER_BUILD_MODE没设置,就会用Xcode build configuration name,也就是说如果想用Xcode build config而不是Debug/Profile/Release的话,需要自行设置FLUTTER_BUILD_MODE来告诉flutter生成什么模式的产物。
5、artifact_variant。后续拷贝flutter引擎的时候使用,决定引擎的版本,在podhelper.rb中已经把flutter引擎集成进去了,不过依赖的是flutter工程本身编译模式引入的版本,可能不同,所以在这个脚本之中希望能够重新引入相应模式的engine
6、shell中case in语句,每种模式需要以右括号结束,并支持正则表达式,语句以;;结束
7、LOCAL_ENGINE。如果本地引擎存在,则引擎使用此路径,后续拷贝引擎从这个目录拷贝
8、project_path/.ios。复制Flutter engine 到依赖目录
9、> /dev/null:把 stdout 送到 /dev/null 里面(送入黑洞)。切换脚本执行目录到flutter工程,以便执行flutter命令
10、verbose_flag:是否需要详细日志的输出标记
11、performance_measurement_option:性能监控
12、RunCommand。调用/bin/flutter执行flutter的编译命令,\这里在一行的末尾表示续行,不是转义符。
13、RunCommand popd。为了方便目录间切换,除了cd,bash内建了3条命令pushd,popd,dirs,维持了一个虚拟命令栈。

2、ThinFramework

剥离出单一架构的Framework

ThinFramework() {
  local framework_dir="$1"
  shift
  # 位置参数可以用shift命令左移,不带参数的shift命令相当于shift 1。
  # $1:"$framework_dir",$2:"$ARCHS"
  # 执行了shift后,变成了$1:"$ARCHS"
  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
  LipoExecutable "${executable}" "$@"
}

1、GetFrameworkExecutablePath。获取app.framework/info.plist中的CFBundleExecutable的值App,返回app.framework/App
2、LipoExecutable。

# 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
      if lipo -output "${output}" -extract "${arch}" "${executable}"; 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
}

-a:将分裂后的字段依次存储到指定的数组中。<<< 就是将后面的内容作为前面命令的标准输入。
lipo -info:查看 Framework 支持的CPU架构。如:armv7 armv7s arm64
-extract:抽取出单一构架
-create:合并指定的多个架构为一个Framework

3、ThinAppFrameworks
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
}

抽取Runner/Frameworks目录下的framework的单一构架

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

  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
  # doesn't exist.
  local flutter_ios_out_folder="${project_path}/.ios/Flutter"
  local flutter_ios_engine_folder="${project_path}/.ios/Flutter/engine"
  if [[ ! -d ${flutter_ios_out_folder} ]]; then
    flutter_ios_out_folder="${project_path}/ios/Flutter"
    flutter_ios_engine_folder="${project_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="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
  RunCommand mkdir -p -- "${xcode_frameworks_dir}"
  RunCommand rsync -av --delete "${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.

  # 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}/"
  if [[ "$ACTION" != "install" || "$ENABLE_BITCODE" == "NO" ]]; then
    # Strip bitcode from the destination unless archiving, or if bitcode is disabled entirely.
    RunCommand "${DT_TOOLCHAIN_DIR}"/usr/bin/bitcode_strip "${flutter_ios_engine_folder}/Flutter.framework/Flutter" -r -o "${xcode_frameworks_dir}/Flutter.framework/Flutter"
  fi

  # Sign the binaries we moved.
  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"
  fi
}

1、rsync -av --delete:同步目录,多余文件删除(要求目标目录要和源目录保持同步,目标目录中多余的文件都要删除)
2、给二进制文件/App.framework/App和/Flutter.framework/Flutter签名。

总结:将/App.framework和/Flutter.framework嵌入app内,然后对/App和/Flutter进行签名。

EmbedAndThinFrameworks() {
  EmbedFlutterFrameworks
  ThinAppFrameworks
}

疑问:
1、sh中用到的一些变量,在哪儿赋值的?

你可能感兴趣的:(xcode_backend.sh源码解析)