319 lines
7.8 KiB
Bash
Executable File
319 lines
7.8 KiB
Bash
Executable File
#!/bin/bash
|
||
set -e
|
||
|
||
phpVer="7.4"
|
||
installRector="false"
|
||
codeRootDir=$PWD
|
||
composerDir="$(dirname "$(realpath "$0")")"
|
||
outputDir="$PWD/build"
|
||
|
||
helper() {
|
||
cat << EOF
|
||
Usage: $0 -p version dirs...
|
||
Arguments:
|
||
-p php版本号,如 7.4
|
||
可以指定多个版本,用逗号分隔,如 7.4,7.0,5.4
|
||
-i 在 misc 目录执行 composer install, 安装 rector
|
||
-r 指定要降级的代码根目录, 默认为当前目录
|
||
-o 降级补丁包输出目录
|
||
EOF
|
||
}
|
||
|
||
# 处理命令行参数, 获取要降级的目录
|
||
[ $# -eq 0 ] && helper
|
||
while getopts "ir:p:o:" opt; do
|
||
case $opt in
|
||
"p")
|
||
phpVer=$OPTARG
|
||
;;
|
||
"r")
|
||
codeRootDir=$(realpath "$OPTARG")
|
||
;;
|
||
"i")
|
||
installRector="true"
|
||
;;
|
||
"o")
|
||
outputDir=$OPTARG
|
||
;;
|
||
*)
|
||
helper
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
shift $((OPTIND - 1))
|
||
|
||
if [ "$#" -eq 0 ];then
|
||
echo "need at least one file or directory"
|
||
exit 2
|
||
fi
|
||
|
||
# 处理降级目录路径
|
||
downGradeDirs=()
|
||
for d in "$@"
|
||
do
|
||
targetDir="$codeRootDir/$d"
|
||
test -d "$targetDir" || (echo "$d ($targetDir) is not a directory, abort."; exit 2)
|
||
downGradeDirs+=("${targetDir}")
|
||
done
|
||
# 结束命令行参数处理
|
||
|
||
excludeRegexList=(
|
||
module/holiday/ext/
|
||
lib/purifier/HTMLPurifier.autoload-legacy.php
|
||
lib/sqlparser/vendor/
|
||
)
|
||
|
||
##################################
|
||
# 在非容器环境,返回 cpu 核数减一
|
||
# 在容器环境,如未对 cpu 做限制,返回宿主机核数减一,否则按实际限制的 cpu 核数
|
||
##################################
|
||
quotaFile="/sys/fs/cgroup/cpu.max"
|
||
getCpuQuota() {
|
||
if [ -f $quotaFile ];then
|
||
quota=$(cut -d ' ' -f 1 $quotaFile)
|
||
period=$(cut -d ' ' -f 2 $quotaFile)
|
||
if [ "$quota" = "max" ];then
|
||
echo $(($(nproc)-1))
|
||
return
|
||
else
|
||
echo $((quota/period))
|
||
return
|
||
fi
|
||
else
|
||
echo $(($(nproc)-1))
|
||
return
|
||
fi
|
||
}
|
||
|
||
# 检查要降级版本的 php 命令行是否存在,将用于校验语法
|
||
existsPHPCli() {
|
||
phpVersion="$1"
|
||
which php"${phpVersion}" >/dev/null
|
||
}
|
||
|
||
#########################################
|
||
# 执行降级命令
|
||
# 参数:
|
||
# 1. 降级版本, 如 7.4, 7.0
|
||
# 2. 关闭缓存 (可选项), nocache
|
||
# 3. 并行执行 (可选项), parallel
|
||
# * 降级目录列表
|
||
# 说明
|
||
# 1. 由于降级命令有可能会发生异常,故设置了重试,
|
||
# 在函数运行时,缓存目录不变,故重试时会减少部分时间
|
||
# 2. 并行执行的个数,根据 cpu 最大核数减一处理,如果是容器环境,按分配的核数
|
||
# 并行执行需要安装 parallel 包
|
||
##########################################
|
||
maxRectorRetries=3
|
||
downGradeDir() {
|
||
rectorCmd="rector"
|
||
|
||
export PHP_VERSION=${1/./}
|
||
shift
|
||
|
||
if [ "$1" = "nocache" ];then
|
||
rectorCmd="rector-nocache"
|
||
shift
|
||
fi
|
||
|
||
withParallel="false"
|
||
if [ "$1" = "parallel" ];then
|
||
withParallel="true"
|
||
shift
|
||
fi
|
||
|
||
downDirs=("$@")
|
||
echo "set PHP_VERSION to $PHP_VERSION"
|
||
echo "do downgrade on dir ${downDirs[*]}"
|
||
|
||
lastDir=$PWD
|
||
cd "$composerDir" || exit
|
||
rectorResult=0
|
||
|
||
set +e
|
||
for ((i=0;i<maxRectorRetries;i++)) {
|
||
if [ "$withParallel" = "true" ];then
|
||
quotaCpu=$(getCpuQuota)
|
||
parallel -j "$quotaCpu" -k composer "${rectorCmd}" ::: "${downDirs[@]}"
|
||
else
|
||
composer ${rectorCmd} -- "${downDirs[@]}"
|
||
fi
|
||
|
||
rectorResult="$?"
|
||
if [ "$rectorResult" -eq 0 ];then
|
||
break
|
||
fi
|
||
}
|
||
set -e
|
||
|
||
if [ "$rectorResult" -ne 0 ];then
|
||
echo "Rector failed"
|
||
return $rectorResult
|
||
fi
|
||
|
||
cd "$lastDir" || exit
|
||
}
|
||
|
||
############################
|
||
# 校验单个 php 文件语法
|
||
# 返回值
|
||
# 0 没有语法错误
|
||
# 1 语法异常,或是执行时异常
|
||
############################
|
||
syntaxCheck() {
|
||
phpVersion="$1"
|
||
phpFilePath="$2"
|
||
|
||
phpCli=$(which php"${phpVersion}")
|
||
|
||
$phpCli -l "$phpFilePath" | grep -v 'No syntax errors detected' && return 1 || return 0
|
||
}
|
||
|
||
########################################
|
||
# 确保php文件语法正确
|
||
# 第一次校验失败后,对文件所在目录执行一次降级
|
||
# 再做第二次校验,如继续失败,返回错误
|
||
########################################
|
||
ensureSyntaxPassed() {
|
||
phpVersion="$1"
|
||
phpFilePath="$2"
|
||
beforeMD5=$(md5sum "$phpFilePath" | awk '{print $1}')
|
||
|
||
if syntaxCheck "$phpVersion" "$phpFilePath";then
|
||
echo "Syntax OK, $phpFilePath"
|
||
else
|
||
downGradeDir "$phpVersion" "nocache" "$(dirname "$phpFilePath")"
|
||
afterMD5=$(md5sum "$phpFilePath" | awk '{print $1}')
|
||
if ! syntaxCheck "$phpVersion" "$phpFilePath";then
|
||
echo "downgrade failed"
|
||
echo "md5 compare (before:$beforeMD5) (after:$afterMD5)"
|
||
return 1
|
||
else
|
||
echo "Syntax OK, $phpFilePath"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
#########################################
|
||
# 将变更的文件打包
|
||
# 判断条件为文件修改时间晚于脚本开始执行的时间
|
||
#########################################
|
||
archiveChangedFiles() {
|
||
phpVersion="$1"
|
||
pointTime=$2
|
||
archiveDir=$3
|
||
|
||
find "$archiveDir" -type f -name '*.php' -newermt "$pointTime" -printf "%P\n" > /tmp/changedFiles
|
||
|
||
lastDir=$PWD
|
||
cd "$archiveDir" || exit 1
|
||
mkdir -p build/
|
||
test -d "$outputDir" || mkdir -pv "$outputDir"
|
||
|
||
downPatchFile="${outputDir}/downgrade-php$phpVersion.tar.gz"
|
||
tar zcf "$downPatchFile" --files-from=/tmp/changedFiles
|
||
echo "Release downgrade patch file: $downPatchFile"
|
||
|
||
cd "$lastDir"
|
||
}
|
||
|
||
# 正则匹配要排除的文件,跳过校验
|
||
matchExclude() {
|
||
filePath="$1"
|
||
for reg in "${excludeRegexList[@]}"
|
||
do
|
||
if [[ "$filePath" =~ $reg ]];then
|
||
echo "true"
|
||
return
|
||
fi
|
||
done
|
||
echo "false"
|
||
}
|
||
|
||
#############################
|
||
# 对单个 php 版本降级的主函数
|
||
# 参数:
|
||
# 1. php 版本
|
||
# 2. 起始时间, 用于比较降级后变更的文件
|
||
# * 降级目录列表
|
||
# 步骤:
|
||
# 设置临时缓存目录,执行降级
|
||
# 对降级目录里所有 php 文件校验语法,确保通过
|
||
# 打包变更文件
|
||
########################################
|
||
DownGrade() {
|
||
phpVersion="$1"
|
||
pointTime="$2"
|
||
shift 2
|
||
|
||
downDirs=("$@")
|
||
RECTOR_CACHE_DIR=$(mktemp -d)
|
||
export RECTOR_CACHE_DIR
|
||
|
||
startTime="$(date +%s)"
|
||
|
||
if which parallel >/dev/null ;then
|
||
downGradeDir "$phpVersion" parallel "${downDirs[@]}"
|
||
else
|
||
for d in "${downDirs[@]}"
|
||
do
|
||
downGradeDir "$phpVersion" "$d"
|
||
done
|
||
fi
|
||
|
||
for f in $(find "${downGradeDirs[@]}" -type f -name '*.php')
|
||
do
|
||
shouldIgnore=$(matchExclude "$f")
|
||
if [ "$shouldIgnore" = "true" ];then
|
||
echo "$f is excluded"
|
||
continue
|
||
fi
|
||
ensureSyntaxPassed "$phpVersion" "$f"
|
||
done
|
||
|
||
archiveChangedFiles "$phpVersion" "$pointTime" "$codeRootDir"
|
||
test -d "$RECTOR_CACHE_DIR" && rm -rf "$RECTOR_CACHE_DIR"
|
||
|
||
endTime="$(date +%s)"
|
||
echo "downgrade to ${phpVersion} cost $((endTime-startTime)) seconds"
|
||
echo
|
||
}
|
||
|
||
# 设置脚本启动时间
|
||
startPointTime=$(date +'%Y-%m-%d %H:%M:%S')
|
||
|
||
# 执行 composer 组件安装
|
||
if [ "$installRector" = "true" ];then
|
||
lastDir=$PWD
|
||
cd "$composerDir" || exit 1
|
||
|
||
composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/
|
||
composer install
|
||
|
||
cd "$lastDir" || exit 1
|
||
fi
|
||
|
||
# 检查 -p 参数, 执行多版本降级或是单版本降级
|
||
if [[ "$phpVer" =~ ^.*, ]];then
|
||
IFS=',' read -r -a verList <<< "$phpVer"
|
||
for v in "${verList[@]}"
|
||
do
|
||
existsPHPCli "$v" || (echo "php${v} is not found, abort."; exit 3)
|
||
done
|
||
|
||
for v in "${verList[@]}"
|
||
do
|
||
DownGrade "$v" "$startPointTime" "${downGradeDirs[@]}"
|
||
done
|
||
else
|
||
existsPHPCli "$phpVer" || (echo "php${phpVer} is not found, abort."; exit 3)
|
||
DownGrade "$phpVer" "$startPointTime" "${downGradeDirs[@]}"
|
||
fi
|
||
|
||
|
||
|
||
# syntaxCheck "$phpVer" /code/module/pipeline/model.php
|
||
# downGradeDir "$phpVer" "${downGradeDirs[@]}"
|
||
# archiveChangedFiles "$t" "$codeRootDir" |