今天学习flutter UI 写的太多有点恶心,今天就想学点别的。本来打算看点源码的东西,但是一个人的力量太小了。因此就找点简单的可以看明白的东西学习下。因此,特此分析下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 $?
}
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到
$? 上个命令的退出状态,或函数的返回值。
$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。
但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。
$? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。
退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1。
-n 判断变量的值,是否为空 ,变量的值,为空,返回1,为false,变量的值,非空,返回0,为true
这是一个shell脚本函数的定义
- 判断是否设置了全局变量VERBOSE_SCRIPT_LOGGING(该变量用来打印日志的)
- 设置了全局变量,那么就打印出传入该函数的命令
- 执行该命令
- 返回执行上述命令的返回值
这里,我在该函数中加入了一行代码
RunCommand() {
VERBOSE_SCRIPT_LOGGING="1.0.0"
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
}
控制台输出结果
第二段
StreamOutput() {
if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
fi
}
> 写入文件
这个函数目的
判断是否配置全局变量SCRIPT_OUTPUT_STREAM_FILE(文件输出路径),要是配置了,那么就将第二个参数$1 写入该文件中
这里我配置了下本地变量
StreamOutput() {
SCRIPT_OUTPUT_STREAM_FILE="test.txt"
if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
fi
}
该文件打印结果是
第三段
EchoError() {
echo "$@" 1>&2
}
shell上:
0表示标准输入
1表示标准输出
2表示标准错误输出
> 默认为标准输出重定向,与 1> 相同
2>&1 意思是把 标准错误输出 重定向到 标准输出.
&>file 意思是把 标准输出 和 标准错误输出 都重定向到文件file中
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
}
-e filename 如果 filename存在,则为真
-h filename 如果filename存在且是一个连接(快捷方式),则为真
这个函数就明白了含义
判断$1 文件存不存在,不存在返回 -1 存在返回 0
第五段
这里出现一个很大的shell 函数 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
输出结果
因此我们知道工程是配置了FLUTTER_APPLICATION_PATH路径的了
该地方就是获取下工程路径而已。
5.2
local target_path="lib/main.dart"
if [[ -n "$FLUTTER_TARGET" ]]; then
target_path="${FLUTTER_TARGET}"
fi
这里和上面的方式一样简单,略过,打印结果
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中,$( )与
(反引号)都是用来作命令替换的。
命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。
${ }变量替换
tr 用来从标准输入中通过替换或删除操作进行字符转换.tr主要用于删除文件中控制字符或进行字符转换.使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换.tr刚执行时,字符串1中的字符被映射到字符串2中的字符,然后转换操作开始.
tr的一般格式:tr -c -d -s ["string1_to_translate_from"] ["string2_to_triampsulata_te_to"]
|tr "[:upper:]" "[:lower:] 意思就是将大写转换成小写字符
${file:-my.file.txt} :假如 $file 没有设定或为空值,则使用 my.file.txt 作传回值。 (非空值时不作处理)
因此,build_mode 这里就能看懂了
1.获取变量{CONFIGURATION}的值
- 将获取到的值转换成小写赋值给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"
输出结果
- case是多分支语句判断
- "release" 字符串里面的* 是通配符,代表只要字符串里面包含realease 的字符串
case语句的意思是
要是我们获取的build_mode变量中包含 release ,那么就设置build_mode 的值是release,而变量artifact_variant 设置成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版本。
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 需要时创建上层目录,如目录早已存在则不当作错误
该段代码分析
1.获取framework_path的路径
2.验证下 framework_path 是否存在
3.验证下project_path 路径是否存在
4.设置derived_dir默认配置路径
5.要是${project_path}/.ios
存在, derived_dir 使用配置的路径
6创建derived_dir路径
7.验证derived_dir是否存在
8.删除derived_dir下面的App.framework
9.生成几个本地变量
10.配置了FLUTTER_ENGINE变量就设置flutter_engine_flag 参数路径。默认是没有设置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 参数
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命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' { } ;,注意{ }和\;之间的空格。
- chmod a-w 取消目录下的所有文件可写权限 。
- 判断{project_path}/.ios/Flutter的engine文件夹,重新创建
2.copy Flutter.podspec 进入engine文件夹
3.copy Flutter.framework 进入engine文件夹
4.要是路径不存在。
5.删除Flutter.framework ,重新复制一份flutter进入
6.设置Flutter.framework 权限.改路径下的文件没有写的权限
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 这条命令的作用是将标准输出1重定向到/dev/null中。/dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。
Pushd是Windows操作系统cmd下的一个命令,作用是保存当前目录以供 POPD 命令使用,然后改到指定的目录。
这段代码的意思
1.保存project_path路径,压栈
2.判断target_path 是否存在
3 判断是否需要打印日志(VERBOSE_SCRIPT_LOGGING),需要就设置变量verbose_flag参数
- 要是FLUTTER_BUILD_DIR 变量使用该变量,否则使用build作为build_dir的值
5.设置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的变量替换,进行变量内容的替换,把与pattern匹配的部分替换为string的内容
=~是正在表达式匹配
.*在正则表达式中表示是指任何字符0个或多个
-
xcrun 命令的使用我是通过在中断man xcrun 命令查看的
-
dsymutil 工具是用来生成dsym文件的,我也是通过man dsymutil来查看该命令的使用
dsymutil -o 后面跟文件命令,代表输出文件路径
strip 命令也是通过man strip 查看,该命令就是剥掉一些符号信息和调试信息,使文件变小
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 输出文案(就是运行到哪里的标记,没有配置输出文件就不会起作用)
2 获取archs 变量,将空格用,分割
3 检查archs变量是不是 i386 或者x86_64,是就打印错误日志,退出
4 控制flutter 命令 (这个命令具体干嘛的,暂时不深究)
5 判断运行flutter 命令是否成功,不成功就退出
6 输出flutter编译成功命令
7 声明变量app_framework 指向编译文件输出文件路径
8 将编译输出文件copy到 derived_dir变量所在路径下
9 打印下面将声明dsym文件
10 创建dsym文件夹
11 运行xrun,将生成的framework文件的dsym文件输出到指定文件夹
12.判断是否生成dsym文件,没有打印错误日志退出
13 删除debug没用的一些符号表
14 判断是否删除一些已经debug过的符号表
debug 模式
知识点
- read命令 -p(提示语句) -n(字符个数) -t(等待时间) -s(不回显) -a :将内容读入到数值中
- <<< 就是将后面的内容作为前面命令的标准输入
- for arch in "${archs[@]}" 遍历数组archs
1 创建文件夹
2 根据arch变量 设置arch_flags 参数
3 控制clang 命令(暂时不知道该命令啥用)
4 获取plist路径
- 将该plist文件copy 到 app.framework中
- 设置预编译参数,当目前不是debug 并且不是x86_64 结构体的时候,设置该参数
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 获取执行app的路径
2 获取archs 参数
3 输出文件命名
4 获取下可执行文件的info信息
5 如果 可执行文件是 not-fat文件,但是却不是arch变量里面的及结构体,输出错误 。要是是not fat 并且和arch一样,那么不做处理(其实就是对not fat文件校验啦。不做处理)
6 执行lipo 命令 将该结构体输出到执行文件
-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.
只有一个结构体的 not fat文件
7 执行成功,保存输出文件路径到all_executables.不成功退出
8 循环结束后,读取all_executables 中的结构体
9 lipo 合并所有的结构体 到merged 变量
10 copy merged 路径下的文件到可执行程序应该的位置
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的用法位置参数可以用shift命令左移。比如shift 3表示原来的1,原来的2等等,原来的2、0不移动
1 获取第一个参数
2 参数左移动
3 获取下plist文件路径
4 获取app执行路径
5 执行 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 执行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 创建该文件夹
6.将app.framwork copy到该路径下
7 删除 路径下的Flutter.framework ,重新copy一份进入该文件夹下
8 签名
该函数是让flutter 和app 内嵌到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
根据传入的参数不同执行不同的命令
到这里我们把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 的参数了
- -x <language> Treat subsequent input files as having type language. (这里我们知道编译使用的c语言编译)
- -arch <architecture> Specify the architecture to build for. (代表生成的build文件结构体类型)
- dynamiclib 连接动态库的意思吧(man clang 和clang -help没找到该命令)
- -Xlinker <arg> Pass <arg> to the linker
举例说明
该命令是用来传递给链接器ld
Xlinker后面跟的参数第一个是空格
对于传递“-assert definitions”命令给ld来说,Xlinker要一下子传递两个参数需要写两次“Xlinker”,比如-Xlinker -assert -Xlinker defintions而不能一下子写成-Xlinker "-assert definitions"因为链接器会认为这是一个参数,而不是两个参数。如果此时你用的是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 文件,会从这个选项里指定的地方去找。对于交叉编译,交叉编译链接器需已经配置 --with-sysroot 选项才能起作用。也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时查找需要加载的动态库。-rpath-link 则只用于链接时查找。
-o 输出文件路径
+-install_name 命令@executable_path 这个变量表示可执行程序所在的目录.
@loader_path 这个变量表示每一个被加载的 binary (包括App, dylib, framework,plugin等) 所在的目录.
@rpath 只是一个保存着一个或多个路径的变量.
gcc选项-xlink
-rpath
OS X 下动态库的引用
@executable_path ,@loader_path,@rpath