狗哥秃头路【2】狗哥花了一天注释的tensorflow-on-arm.sh脚本
来源
https://github.com/lhelontra/tensorflow-on-arm
注:注释还有不完整和可能错误的部分,会逐渐完善,也欢迎指正。
#!/bin/bash
# build_tensorflow -*- shell-script -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2017 Leonardo Lontra
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#将一个文件普通名传给传给$1,并判断这个文件是否存在
[ -f "$1" ] && {
source "$1"#存在的话(返回真)就在shell执行这个文件
} || { #如果不存在就输出以下语句并退出
echo -ne "Use: $0 <config>\n\tFor prepare environment only, uses: $0 <config> prepare\n"
exit 1
}
#realpath 用于获取指定目录或文件的绝对路径
#$(dirname $0)切换到脚本所在的目录
#令DIR指向当前脚本文件所在的绝对路径
DIR="$(realpath $(dirname $0))"
#执行脚本所在目录下的patch.sh
source "${DIR}/patch.sh"
# builtin variables
RED='\033[0;31m'
BLUE='\033[1;36m'
#关闭所有属性
NC='\033[0m'
#如果没有指定版本则默认使用PYTHON3
TF_PYTHON_VERSION=${TF_PYTHON_VERSION:-"3"}
#默认的一些配置
TF_VERSION=${TF_VERSION:-"v1.14.0"}
TF_BUILD_OUTPUT=${TF_BUILD_OUTPUT:-"/tmp/tensorflow_pkg"}
BAZEL_VERSION=${BAZEL_VERSION:-"0.24.1"}
CROSSTOOL_WHEEL_ARCH=${CROSSTOOL_WHEEL_ARCH:-"any"}
TF_GIT_URL=${TF_GIT_URL:-"https://github.com/tensorflow/tensorflow"}
WORKDIR=${WORKDIR:-"$DIR"}
#判断bazel命令是否存在
BAZEL_BIN="$(command -v bazel)"
function set_selected_python_default() {
#判断当前路径下python能否执行
PYTHON_BIN_PATH=$(command -v python${TF_PYTHON_VERSION})
default_python=$(command -v python)
[ -z "$default_python" ] && {
default_python="$(dirname $PYTHON_BIN_PATH)/python"
}
#删除默认
rm -f $default_python &>/dev/null
#强制创建软连接
ln -sf $PYTHON_BIN_PATH $default_python &>/dev/null
}
#报错信息函数
function log_failure_msg() {
echo -ne "[${RED}ERROR${NC}] $@\n"
}
#消息汇报函数
function log_app_msg() {
echo -ne "[${BLUE}INFO${NC}] $@\n"
}
#创建工作文件目录
function create_workdir()
{
WORKDIR=${WORKDIR}/sources/
#如果不存在这个目录就创建,创建失败就报错并退出
if [ ! -d $WORKDIR ]; then
mkdir -p ${WORKDIR} || {
log_failure_msg "error when creates workdir $WORKDIR"
exit 1
}
fi
return 0
}
#构建bazel的函数
function build_bazel()
{
#创建目录
mkdir -p ${WORKDIR}/bin/
#如果版本不同或未找到,则强制编译Bazel
if [ -z "$BAZEL_BIN" ] || [ "$($BAZEL_BIN version | grep -i 'label' | awk '{ print $3 }' | tr -d '-')" != "${BAZEL_VERSION}" ]; then
BAZEL_BIN="${WORKDIR}/bin/bazel-${BAZEL_VERSION}"
fi
#默认路径
PATH="${WORKDIR}/bin/:${PATH}"
#判断是否已安装bazel
if [ -f "$BAZEL_BIN" ]; then
log_app_msg "bazel already installed."
#确保使用版本的正确
rm -f ${WORKDIR}/bin/bazel &>/dev/null
#bazel建立软连接后删除???
ln -sf "${WORKDIR}/bin/bazel-${BAZEL_VERSION}" "${WORKDIR}/bin/bazel" &>/dev/null
return 0
fi
set_selected_python_default
#切换到工作路径
cd $WORKDIR
#如果不存在就在github上下载到指定位置
if [ ! -f bazel-${BAZEL_VERSION}-dist.zip ]; then
wget --no-check-certificate https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip
fi
#如果目录不存在就创建,并且解压到指定目录并删除压缩包
if [ ! -d bazel-${BAZEL_VERSION} ]; then
mkdir bazel-${BAZEL_VERSION}
unzip bazel-${BAZEL_VERSION}-dist.zip -d bazel-${BAZEL_VERSION}/
rm -f bazel-${BAZEL_VERSION}-dist.zip
#到解压后文件的目录
cd bazel-${BAZEL_VERSION}/
#?如果bazel补丁文件存在则执行
if [ "$BAZEL_PATCH" == "yes" ]; then
bazel_patch || {
log_failure_msg "error when apply patch"
exit 1
}
fi
else
cd bazel-${BAZEL_VERSION}/
fi
#执行编译
./compile.sh
#如果缺少文件就报错
if [ ! -f ./output/bazel ]; then
log_failure_msg "error when compile bazel"
exit 1
fi
#赋予执行权限
chmod +x output/bazel
#改名
mv output/bazel "${WORKDIR}/bin/bazel-${BAZEL_VERSION}"
#建立软连接并删除前者
ln -sf "${WORKDIR}/bin/bazel-${BAZEL_VERSION}" "${WORKDIR}/bin/bazel" &>/dev/null
return 0
}
#工具链函数
function toolchain()
{
#检查编译器是否存在,存在则检查URL
[ "$CROSSTOOL_COMPILER" != "yes" ] || [ -z "$CROSSTOOL_URL" ] && return 0
#设置
CROSSTOOL_DIR="${WORKDIR}/toolchain/${CROSSTOOL_DIR}/"
#检查交叉编译器的路径是否存在,不存在就创建
[ ! -d "${CROSSTOOL_DIR}/${CROSSTOOL_NAME}/bin/" ] && {
mkdir -p ${WORKDIR}/toolchain/
#下载压缩包,如果失败就报错
wget --no-check-certificate $CROSSTOOL_URL -O toolchain.tar.xz || {
log_failure_msg "error when download crosstool"
exit 1
}
#解压到指定路径,解压如果错误报错并退出
tar xf toolchain.tar.xz -C ${WORKDIR}/toolchain/ || {
log_failure_msg "error when extract crosstool"
exit 1
}
#删除压缩包
rm toolchain.tar.xz &>/dev/null
}
}
#下载tensorflow的函数
function download_tensorflow()
{
#到工作路径下
cd ${WORKDIR}
#检测是否存在tensorflow的文件,不存在就去git上clone
if [ ! -d tensorflow ]; then
git clone --recurse-submodules ${TF_GIT_URL} || return 1
cd tensorflow/
else
cd tensorflow/
#???清除操作
[ "$1" != "noclean" ] && $BAZEL_BIN clean &>/dev/null
#退回当前版本
git reset --hard
#从工作目录中删除所有没有tracked过的文件
git clean -f -d
#切换到master分支
git checkout master
#删除本地分支
git branch -D __temp__
#更新
git pull
fi
#切换分支,失败则报错
git checkout ${TF_VERSION} || {
log_failure_msg "error when using tensorflow version ${TF_VERSION}"
exit 1
}
#创建一个临时分支以应用一些补丁并重新使用克隆的文件夹
git checkout -b __temp__
#设置git local config以应用补丁
git config user.email "temp@example.com"
git config user.name "temp"
#检测tensorflow补丁是否存在
if [ "$TF_PATCH" == "yes" ]; then
tf_patch || {
log_failure_msg "error when apply patch"
exit 1
}
fi
#检测交叉编译工具并打补丁?
if [ ! -z "$CROSSTOOL_DIR" ] && [ ! -z "$CROSSTOOL_NAME" ]; then
tf_toolchain_patch "$CROSSTOOL_NAME" "$CROSSTOOL_DIR" "$CROSSTOOL_ROOT" "$CROSSTOOL_EXTRA_INCLUDE" || {
log_failure_msg "error when apply crosstool patch"
exit 1
}
fi
#将我们需要提交的代码从工作区添加到暂存区,就是告诉git系统,我们要提交哪些文件
#之后就可以使用git commit命令进行提交了
git add .
git commit -m "temp modifications"
return 0
}
function configure_tensorflow()
{
# 配置 tensorflow
#到tensorflow目录下
cd ${WORKDIR}/tensorflow
#清空目录?
[ "$1" != "noclean" ] && $BAZEL_BIN clean
#配置环境
export PYTHON_BIN_PATH=$(command -v python${TF_PYTHON_VERSION})
export ${TF_BUILD_VARS}
set_selected_python_default
#如果启用了need_cuda,则搜索sdk
if [ "$TF_NEED_CUDA" == "1" ]; then
local nvcc_path=$(command -v nvcc)#局部变量
#如果存在,设置局部变量
if [ ! -z "$nvcc_path" ]; then
local cuda_location=$(echo $nvcc_path | sed 's/\/bin\/nvcc//')#对文件中的所有行编号,但只显示非空白行的行号
#设置为在版本文本中第三列,以.为分割,取前两个
local cuda_version=$(cat "${cuda_location}/version.txt" | awk '{ print $3 }' | cut -d'.' -f-2)
#查找 截取.为分割第三个
local cudnn_version=$(readlink $(find "${cuda_location}/" -iname '*libcudnn.so') | cut -d'.' -f3)
#配置环境变量
export CUDA_TOOLKIT_PATH="$cuda_location"
export TF_CUDA_VERSION=$cuda_version
export TF_CUDNN_VERSION=$cudnn_version
else
export TF_NEED_CUDA=0
fi
fi
#开始使用此python文件配置,失败报错
yes '' | $PYTHON_BIN_PATH configure.py || {
log_failure_msg "error when configure tensorflow"
exit 1
}
return 0
}
#配置tensorflow的函数
function build_tensorflow()
{
cd ${WORKDIR}/tensorflow
#检测文件是否都存在
if [ ! -z "$BAZEL_AVALIABLE_RAM" ] && [ ! -z "$BAZEL_AVALIABLE_CPU" ] && [ ! -z "$BAZEL_AVALIABLE_IO" ]; then
BAZEL_LOCAL_RESOURCES="--local_resources ${BAZEL_AVALIABLE_RAM},${BAZEL_AVALIABLE_CPU},${BAZEL_AVALIABLE_IO}"
fi
#如果?,在BAZEL_EXTRA_FLAGS添加
[[ "${BAZEL_EXTRA_FLAGS}" == *"build_pip_package"* ]] && BAZEL_EXTRA_FLAGS+=" --python_path=python${TF_PYTHON_VERSION}"
#build pip package???
$BAZEL_BIN build ${BAZEL_LOCAL_RESOURCES} -c opt ${BAZEL_COPT_FLAGS} --verbose_failures ${BAZEL_EXTRA_FLAGS} || return 1
#如有需要,制造whl
[[ "${BAZEL_EXTRA_FLAGS}" == *"build_pip_package"* ]] && {
unset BDIST_OPTS
#如果激活了交叉编译,则构建通用whl
if [ ! -z "$CROSSTOOL_DIR" ] && [ ! -z "$CROSSTOOL_NAME" ]; then
export BDIST_OPTS="--universal"
fi
#创建目录
mkdir -p ${TF_BUILD_OUTPUT} || {
log_failure_msg "error when creates output dir $TF_BUILD_OUTPUT"
exit 1
}
# 构建whl
bazel-bin/tensorflow/tools/pip_package/build_pip_package $TF_BUILD_OUTPUT || return 1
if [ ! -z "$BDIST_OPTS" ]; then
"#f设置为 前者目录查找whl文件内第一行
local f="${TF_BUILD_OUTPUT}/$(ls -t $TF_BUILD_OUTPUT | grep -i '.whl' | head -n1)
#new_f为f的???(这段离谱的正则表达式在结尾解释)
local new_f="$(echo $f | sed -rn "s/tensorflow-([^-]+)-([^-]+)-.*/tensorflow-\1-\2-none-${CROSSTOOL_WHEEL_ARCH}.whl/p")"
#改名
mv $f $new_f
log_app_msg "wheel was renamed of $f for $new_f"
fi
}
# 复制库文件(如果需要)
[[ "${BAZEL_EXTRA_FLAGS}" == *"libtensorflow"* ]] && {
#收集库文件到TF_BUILD_OUTPUT
cp bazel-bin/tensorflow/libtensorflow* $TF_BUILD_OUTPUT &>/dev/null
cp bazel-bin/tensorflow/lite/libtensorflowlite* $TF_BUILD_OUTPUT &>/dev/null
cp tensorflow/c/c_api.h $TF_BUILD_OUTPUT &>/dev/null
log_app_msg "Library files moved to $TF_BUILD_OUTPUT"
}
log_app_msg "Done."
}
#准备环境的函数
function prepare_env()
{
# prepare environment for compiling
create_workdir
build_bazel
toolchain
download_tensorflow "$1"
echo -ne "Workdir: \t${WORKDIR}\n"
echo -ne "Bazel binary: \t${BAZEL_BIN}\n"
[ ! -z "$CROSSTOOL_DIR" ] && echo -ne "Toolchain directory:\t${CROSSTOOL_DIR}\n"
}
function main()
{
prepare_env "$1"
configure_tensorflow "$1"
build_tensorflow
}
#如果传入“prepare”则执行环境配置函数,否则执行主函数
[ "$2" == "prepare" ] && prepare_env || main "$2"
存在的问题以及解释
clean命令:clean是bazel命令
*"build_pip_package"*
$BAZEL_BIN build ${BAZEL_LOCAL_RESOURCES} -c opt ${BAZEL_COPT_FLAGS} --verbose_failures ${BAZEL_EXTRA_FLAGS}
local new_f="$(echo $f | sed -rn "s/tensorflow-([^-]+)-([^-]+)-.*/tensorflow-\1-\2-none-${CROSSTOOL_WHEEL_ARCH}.whl/p")"
这段含有正则和sed的命令比较长,逐个分析。
首先是前面 “local new_f =
”说明是定义局部变量。
echo $f |
说明是把前面的变量f 的内容传给后面
后面这段是重点,
sed -rn "s/tensorflow-([^-]+)-([^-]+)-.*/tensorflow-\1-\2-none-${CROSSTOOL_WHEEL_ARCH}.whl/p"
sed命令的r和n参数
r表示含有正则表达式
n表示静默输出,仅显示script处理后的结果
s/表示替换
/p表示打印
除去这些即为正则表达式的内容
tensorflow-([^-]+)-([^-]+)-.*/tensorflow-\1-\2-none-${CROSSTOOL_WHEEL_ARCH}.whl
([^-]+)
()括号是分组用,在这个表达式里没有作用
[^-]意思是不包含-这个字符
+意思是至少一个或多个
总体意思:不包含-的任意字符,字符个数不限,但至少一个字符
.* 就是单个字符匹配任意次,即贪婪匹配 ,此处可以理解为把 -([^-]+)-([^-]+)-匹配到的内容之后的所有内容
整体即为
-([^-]+)-([^-]+)-.*
-{不含“-”的至少一个字符}-{不含“-”的至少一个字符}-{之后所有的字符}
替换后
-{不含“-”的至少一个字符}-{不含“-”的至少一个字符}-none-变量.whl
为了验证,采用在线正则表达式来测试一下:
下面的测试中,图一没加.*正则匹配到了-t-t-和-weqwewq-1-
第二张图由于.*符号,所以把第一次匹配到的-t-t-之后的内容全部输出了。
tensorflow介绍:
http://www.tensorfly.cn/