<\/div><\/div>/g" zentaoxx/extension/xuan/client/view/checkupgrade.html.php
sed -i '/var serverVersions/d' zentaoxx/extension/xuan/client/js/checkupgrade.js
@@ -146,14 +152,20 @@ zentaoxx:
sed -i 's/commonModel::getLicensePropertyValue/extCommonModel::getLicensePropertyValue/g' zentaoxx/extension/xuan/im/control.php
sed -i 's/commonModel::getLicensePropertyValue/extCommonModel::getLicensePropertyValue/g' zentaoxx/extension/xuan/im/model/conference.php
sed -i 's/xxb_/zt_/g' zentaoxx/db/*.sql
+ sed -i 's/commonModel::isLicensedMethod([^\)]*)/false/g' zentaoxx/extension/xuan/conference/model.php
sed -i "s#\$this->app->getModuleRoot() . 'im/apischeme.json'#\$this->app->getExtensionRoot() . 'xuan/im/apischeme.json'#g" zentaoxx/extension/xuan/im/model.php
sed -i "s/'..\/..\/common\/view\/header.html.php'/\$$app->getModuleRoot() . 'common\/view\/header.html.php'/g" zentaoxx/extension/xuan/conference/view/admin.html.php
sed -i "s/'..\/..\/common\/view\/footer.html.php'/\$$app->getModuleRoot() . 'common\/view\/footer.html.php'/g" zentaoxx/extension/xuan/conference/view/admin.html.php
sed -i "s/\$$this->im->userGetChangedPassword()/array()/" zentaoxx/extension/xuan/im/control.php
+ sed -i "s/->app->getModuleExtPath('', /->app->getModuleExtPath(/g" zentaoxx/extension/xuan/im/model/bot.php
+ sed -i "s/\$$this->getModuleExtPath('', /\$$this->getModuleExtPath(/g" zentaoxx/framework/xuanxuan.class.php
+ sed -i "s/, \$$version)\$$/, \$$version = '')/g" zentaoxx/extension/xuan/im/model.php
+ sed -i "s/, \$$version)\$$/, \$$version = '')/g" zentaoxx/extension/xuan/im/model/conference.php
sed -i "/.*->getAllDepts();/d" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
sed -i "s/lang->user->status/lang->user->clientStatus/" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
sed -i "s/.*->getRoleList();/\$$depts = \$$this->im->loadModel('dept')->getDeptPairs();\n\$$deptList = array_map(function(\$$k, \$$v) {return (object)array('id' => \$$k, 'name' => \$$v);}, array_keys(\$$depts), \$$depts);\n\$$roleList = \$$this->im->lang->user->roleList;/" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
echo "ALTER TABLE \`zt_user\` ADD \`pinyin\` varchar(255) NOT NULL DEFAULT '' AFTER \`realname\`;" >> zentaoxx/db/xuanxuan.sql
+ find zentaoxx/extension/xuan/ -name '*.php' -exec sed -i -r 's|->ne(["'\'']0000-00-00 00:00:00["'\''])|->notZeroDatetime()|g; s|["'\'']\)->eq\(["'\'']0000-00-00( 00:00:00)?| is null|g; s|([=!]=) ?["'\'']0000-00-00( 00:00:00)?["'\'']|\1 null|g; s|([^!=]=) ?["'\'']0000-00-00( 00:00:00)?["'\'']|\1 null|g; s|(["'\''])(,.*)\)->eq\(["'\'']0000-00-00( 00:00:00)?["'\'']| is null\1\2|g; s|["'\'']0000-00-00 00:00:00["'\'']|null|g' {} +
mkdir zentaoxx/misc; cp misc/cn2tw.php zentaoxx/misc; cd zentaoxx/misc; php cn2tw.php
cp misc/en2other.php zentaoxx/misc; cd zentaoxx/misc; php en2other.php ../
rm -rf zentaoxx/misc
@@ -253,8 +265,9 @@ enrpm:
rpmbuild -ba ~/rpmbuild/SPECS/zentaopms.spec
cp ~/rpmbuild/RPMS/noarch/zentaoalm-${VERSION}-1.noarch.rpm ./
rm -rf ~/rpmbuild
+cleanAssets:
+ find zentaopms/ -type f \( -name "*.js.map" -o -name "*.gz" \) -delete
ciCommon:
- make clean
make common
ifneq ($(XUANPATH), )
@@ -264,14 +277,20 @@ ciCommon:
endif
make package
+ make cleanAssets
zip -rq -9 ZenTaoPMS.$(VERSION).zip zentaopms
# en
- cd zentaopms/; grep -rl 'zentao.net'|xargs sed -i 's/zentao.net/zentao.pm/g';
- cd zentaopms/; grep -rl 'http://www.zentao.pm'|xargs sed -i 's/http:\/\/www.zentao.pm/https:\/\/www.zentao.pm/g';
- cd zentaopms/config/; echo >> config.php; echo '$$config->isINT = true;' >> config.php
- mv zentaopms zentaoalm
+ cp -a zentaopms zentaoalm
+ cd zentaoalm/; grep -rl 'zentao.net'|xargs sed -i 's/zentao.net/zentao.pm/g';
+ cd zentaoalm/; grep -rl 'http://www.zentao.pm'|xargs sed -i 's/http:\/\/www.zentao.pm/https:\/\/www.zentao.pm/g';
+ cd zentaoalm/config/; echo >> config.php; echo '$$config->isINT = true;' >> config.php
zip -rq -9 ZenTaoALM.$(VERSION).int.zip zentaoalm
- rm -fr zentaoalm
+
+ # downgrade
+ @test "$(DOWNGRADE_ENABLED)" != "true" && echo "skip downgrade" || ./misc/downgrade.sh -p "$(DOWNGRADE_VERSIONS)" -i -r zentaopms -o "$(RELEASE_PATH)" framework/ lib/ module/*
+
+ rm -fr zentaopms zentaoalm
+
# move pms zip to build and release path.
rm -f $(BUILD_PATH)/ZenTao*.zip $(RELEASE_PATH)/ZenTaoPMS.$(VERSION).zip $(RELEASE_PATH)/ZenTaoALM.$(VERSION).int.zip
cp ZenTaoPMS.$(VERSION).zip $(BUILD_PATH)
diff --git a/ci.json b/ci.json
new file mode 100644
index 0000000000..0f652128cf
--- /dev/null
+++ b/ci.json
@@ -0,0 +1,49 @@
+{
+ "pkg": {
+ "xuanxuan": {
+ "gitVersion": "f3895b8a80f269c81813c177ede6e81a86219cd0",
+ "version": "7.2.1"
+ },
+ "zentaoext": {
+ "gitVersion": "master",
+ "gitRepo": "https://git.zcorp.cc/easycorp/zentaoext.git"
+ },
+ "zdoo": {
+ "gitVersion": "master"
+ },
+ "zdooext": {
+ "gitVersion": "master"
+ }
+ },
+ "image": {
+ "public": {
+ "namespace": {
+ "release": "app",
+ "snapshot": "test"
+ }
+ },
+ "internal": {
+ "namespace": {
+ "release": "app",
+ "snapshot": "test"
+ }
+ }
+ },
+ "downgrade": {
+ "enabled": true,
+ "versions": "7.2,7.1,7.0,5.4"
+ },
+ "upload": {
+ "bucket": "download",
+ "nexus": {
+ "release": "easycorp",
+ "snapshot": "easycorp-snapshot"
+ }
+ },
+ "notice": {
+ "users": "qishiyao",
+ "usersX": "qishiyao,hufangzhou",
+ "groupsX": "84be4c6e-02e3-4fdc-b081-318c0c1eca02,31a0008b-6e3e-4b7f-9b7b-396a46b1f8f4",
+ "groups2X": "84be4c6e-02e3-4fdc-b081-318c0c1eca02,31a0008b-6e3e-4b7f-9b7b-396a46b1f8f4,6f5bb3f5-f4e3-4f67-963e-0cc9d59ad3c7"
+ }
+}
diff --git a/misc/ci/Jenkinsfile b/misc/ci/Jenkinsfile
new file mode 100644
index 0000000000..de48d4d9a2
--- /dev/null
+++ b/misc/ci/Jenkinsfile
@@ -0,0 +1,606 @@
+library 'z-common@feat-231024'
+library 'z-zentaopms@feat-231019'
+
+pipeline {
+ agent {
+ kubernetes {
+ inheritFrom "xuanim"
+ yamlFile 'misc/ci/normal.yaml'
+ }
+ }
+
+ options {
+ skipDefaultCheckout()
+ parallelsAlwaysFailFast()
+ overrideIndexTriggers(false)
+ quietPeriod 900
+ }
+
+ parameters {
+ choice choices: ['auto', 'snapshot', 'release'], description: '构建包类型(快照或发版)', name: 'build_kind'
+ booleanParam defaultValue: true, description: '制作源码包', name: 'publish_zip'
+ booleanParam defaultValue: true, description: '制作镜像', name: 'publish_image'
+ booleanParam defaultValue: true, description: '制作一键安装包', name: 'publish_zbox'
+ // booleanParam defaultValue: true, description: '开启代码降级', name: 'DOWNGRADE_ENABLED'
+ string description: '指定一个构建Id用于回放,适用于tag类型的job', name: 'REPLAY_ID', trim: true
+ }
+
+ environment {
+ TZ="Asia/Shanghai"
+
+ ZENTAO_RELEASE_PATH = "${WORKSPACE}/release"
+ XUANXUAN_SRC_PATH = "${WORKSPACE}/xuansrc"
+ SRC_ZDOO_PATH = "${WORKSPACE}/zdoo"
+ SRC_ZDOOEXT_PATH = "${WORKSPACE}/zdooext"
+ SRC_ZENTAOEXT_PATH = "${WORKSPACE}/zentaoext"
+
+ MIDDLE_IMAGE_REPO = "hub.qc.oop.cc/zentao-package-ext"
+ MIDDLE_IMAGE_TAG = """${sh(
+ returnStdout: true,
+ script: 'date +%Y%m%d%H%M-${BUILD_ID}'
+ ).trim()}"""
+
+ MIRROR = "true"
+
+ // 默认从job参数里读取,打开后覆盖
+ PUBLISH_ZIP = "${params.publish_zip}"
+ PUBLISH_IMAGE = "${params.publish_image}"
+ PUBLISH_ZBOX = "${params.publish_zbox}"
+
+ // set to blank for auto-detect from ci.json
+ DOWNGRADE_ENABLED = ""
+ DOWNGRADE_VERSIONS = ""
+ }
+
+ stages {
+
+ stage("Basic Build") {
+ when {
+ anyOf {
+ buildingTag()
+ not {
+ triggeredBy 'BranchEventCause'
+ }
+ }
+ }
+
+ agent {
+ kubernetes {
+ inheritFrom "zentao-package build-docker xuanim"
+ yamlFile 'misc/ci/basic-build.yaml'
+ }
+ }
+
+ stages {
+ stage("Pull") {
+ steps {
+ checkout scm
+ script {
+ def triggeredBy = currentBuild.getBuildCauses()
+ println "Build triggered by: ${triggeredBy}"
+
+ job.DetectTrigger()
+ env.XUANVERSION = sh(returnStdout: true,script: 'jq -r .pkg.xuanxuan.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_GIT_REPO = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitRepo < ci.json').trim()
+ env.ZDOO_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdoo.gitVersion < ci.json').trim()
+ env.ZDOOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdooext.gitVersion < ci.json').trim()
+
+ if (params.build_kind=='auto') {
+ env.BUILD_KIND = "snapshot"
+ if (env.TAG_NAME) {
+ def testStr = env.TAG_NAME
+ def count = testStr.count(/_/)
+ if (count==1) {
+ env.BUILD_KIND = "release"
+ }
+ }
+ } else {
+ env.BUILD_KIND = params.build_kind
+ }
+
+ }
+
+ replayCheckout([
+ [name: "xuansrc", subDir: true, url: "https://git.zcorp.cc/easycorp/xuanxuan.git", credentialsId: "git-zcorp-cc-jenkins-bot-http", branch: "${env.XUANVERSION}", depth: 0],
+ [name: "zdoo", subDir: true, url: "https://git.zcorp.cc/easycorp/zdoo.git", credentialsId: "git-zcorp-cc-jenkins-bot-http", branch: "${env.ZDOO_VERSION}", depth: 2],
+ [name: "zdooext", subDir: true, url: "https://git.zcorp.cc/easycorp/zdooext.git", credentialsId: "git-zcorp-cc-jenkins-bot-http", branch: "${env.ZDOOEXT_VERSION}", depth: 2],
+ [name: "zentaoext", subDir: true, url: "${env.ZENTAOEXT_GIT_REPO}", credentialsId: "git-zcorp-cc-jenkins-bot-http", branch: "${env.ZENTAOEXT_VERSION}", depth: 2]
+ ])
+
+ }
+ }
+
+ stage("Setup Global Env") {
+ steps {
+ sh 'env'
+ script {
+ // GIT_URL GIT_COMMIT isn't export to env by jenkins. fetch by self
+ env.GIT_URL = sh(returnStdout: true, script: 'git config --get remote.origin.url').trim()
+ env.GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
+
+ job.BuildHeader()
+ // env.GIT_TAGGER_NAME = sh(returnStdout: true, script: 'git for-each-ref --format="%(taggername)" refs/tags/$(git tag --points-at HEAD)').trim()
+
+ env.CI_PUBLIC_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.public.namespace.' + env.BUILD_KIND + ' < ci.json').trim()
+ env.CI_INTERNAL_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.internal.namespace.' + env.BUILD_KIND + ' < ci.json').trim()
+
+ def ximUsers = sh(returnStdout: true,script: 'jq -r .notice.users < ci.json').trim()
+ env.XIM_USERS = ximUsers + ',' + env.GIT_TAGGER_NAME
+ if (env.BUILD_KIND=='release') {
+ env.XIM_GROUPS = sh(returnStdout: true,script: 'jq -r .notice.groups2 < ci.json').trim()
+ } else {
+ env.XIM_GROUPS = sh(returnStdout: true,script: 'jq -r .notice.groups < ci.json').trim()
+ }
+
+ env.PMS_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/VERSION').trim()
+ env.BIZ_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/BIZVERSION').trim()
+ env.MAX_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/MAXVERSION').trim()
+ env.IPD_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/IPDVERSION').trim()
+
+ env.DOCKER_CREDENTIALS_ID = 'hub-qucheng-push'
+
+ env.CI_DOWNGRADE_ENABLED = sh(returnStdout: true, script: 'test -n "${DOWNGRADE_ENABLED}" && echo ${DOWNGRADE_ENABLED} || (jq -r .downgrade.enabled < ci.json)').trim()
+ env.QINIU_BUCKET = sh(returnStdout: true, script: 'jq -r .upload.bucket < ci.json').trim()
+
+ if (env.BUILD_KIND=='release') {
+ env.ARTIFACT_SUBGROUP='release'
+ } else {
+ currentDate = sh(returnStdout: true, script: 'date +%Y%m%d').trim()
+ env.ARTIFACT_SUBGROUP = currentDate + '.' + env.GIT_COMMIT[0..6]
+ }
+ env.ARTIFACT_REPOSITORY = sh(returnStdout: true,script: 'jq -r .upload.nexus.' + env.BUILD_KIND + ' < ci.json').trim()
+ env.ARTIFACT_HOST = "nexus.qc.oop.cc"
+ env.ARTIFACT_PROTOCOL = "https"
+ env.ARTIFACT_CRED_ID = "nexus-jenkins"
+
+ def mdPath = "start.md"
+ job.BuildChangeSetMd(mdPath)
+ job.unStashMsgHeader()
+ ximNotify(title: "开始构建禅道", contentFile: mdPath, contentHeaderFile: job.headerFileName())
+ }
+
+ }
+ }
+
+ stage("Build") {
+ when {
+ environment name:'PUBLISH_ZIP', value:'true'
+ }
+ stages {
+ stage("make ciCommon") {
+ steps {
+ withCredentials([gitUsernamePassword(credentialsId: 'git-zcorp-cc-jenkins-bot-http',gitToolName: 'git-tool')]) {
+ container('package') {
+ sh 'mkdir ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
+ sh 'git config --global pull.ff only'
+ sh 'cp -av ${ZENTAO_BUILD_PATH}/adminer www/'
+ sh 'pwd && ls -l && make ciCommon'
+ sh 'ls -l ${ZENTAO_RELEASE_PATH}'
+ }
+ }
+ }
+ }
+
+ stage("zentaoext") {
+ steps {
+ container('package') {
+ sh 'cd $SRC_ZENTAOEXT_PATH && make'
+ sh 'cp ${ZENTAO_BUILD_PATH}/zentao*.zip ./'
+ sh 'cp ${ZENTAO_BUILD_PATH}/docker/Dockerfile.release.ext ./Dockerfile.release.ext'
+ }
+ container('docker') {
+ sh 'docker build --pull -t ${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG} -f Dockerfile.release.ext ${ZENTAO_RELEASE_PATH}'
+ sh 'docker push ${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG}'
+ }
+ script {
+ buildPkg.uploadSource()
+ }
+
+ }
+ }
+
+ }
+ } // End Build
+ }
+ }
+
+ stage("Publish") {
+ environment {
+ OUTPUT_PKG_PATH = "${ZENTAO_RELEASE_PATH}/output"
+ }
+
+ stages {
+ stage("Merge and Upload") {
+ when {
+ environment name:'PUBLISH_ZIP', value:'true'
+ }
+ matrix {
+ agent {
+ kubernetes {
+ containerTemplate {
+ name "package"
+ image "${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG}"
+ command "sleep"
+ args "99d"
+ }
+ yamlFile 'misc/ci/publish-zip.yaml'
+ }
+ }
+ options {
+ skipDefaultCheckout()
+ }
+
+ axes {
+ axis {
+ name "ZLANG"
+ values "cn", "en"
+ }
+ axis {
+ name "PHPVERSION"
+ values "php5.4_5.6", "php7.0", "php7.1", "php7.2_7.4", "k8s.php7.2_7.4", "php8.1", "k8s.php8.1"
+ }
+ }
+ excludes {
+ exclude {
+ axis {
+ name 'ZLANG'
+ values 'en'
+ }
+ axis {
+ name "PHPVERSION"
+ values "k8s.php7.2_7.4", "k8s.php8.1"
+ }
+ }
+ }
+
+ stages {
+
+ stage("ZIP") {
+ environment {
+ ARTIFACT_NAME = """${sh(
+ returnStdout: true,
+ script: 'test ${ZLANG} = cn && echo -n ZenTaoPMS || echo -n ZenTaoALM'
+ ).trim()}"""
+ INT_FLAG = """${sh(
+ returnStdout: true,
+ script: 'test ${ZLANG} = cn && echo -n "int." || echo -n ""'
+ ).trim()}"""
+ }
+
+ stages {
+ stage("package zip") {
+ steps{
+ echo "${env.ZLANG} <=> ${env.PHPVERSION}"
+ container('package') {
+ sh 'mkdir $ZENTAO_RELEASE_PATH'
+ sh '${ZENTAO_BUILD_PATH}/package.sh zip'
+ sh 'mkdir $OUTPUT_PKG_PATH'
+ }
+ }
+ }
+
+ stage("upload zip") {
+ steps {
+ sh 'mkdir ${OUTPUT_PKG_PATH}/${PMS_VERSION}'
+ sh 'cp ${ZENTAO_RELEASE_PATH}/base.zip ${OUTPUT_PKG_PATH}/${PMS_VERSION}/${ARTIFACT_NAME}-${PMS_VERSION}-${PHPVERSION}.zip'
+
+ script {
+ buildPkg.uploadPMS(env.PHPVERSION)
+ // copy php7.2 as php8.0
+ if (env.PHPVERSION=="php7.2_7.4") {
+ buildPkg.uploadPMS('php8.0')
+ sh 'cp ${ZENTAO_RELEASE_PATH}/base.zip ${OUTPUT_PKG_PATH}/${PMS_VERSION}/${ARTIFACT_NAME}-${PMS_VERSION}-php8.0.zip'
+ }
+ buildPkg.uploadExt()
+ }
+
+
+ }
+ } // End upload zip
+
+ stage("syspack") {
+ when {
+ environment name: 'BUILD_KIND', value: 'release'
+ }
+ steps{
+ script {
+ buildPkg.buildRpmAndDeb()
+ }
+ }
+ }
+
+ stage("Upload Qiniu") {
+ when {
+ environment name: 'BUILD_KIND', value: 'release'
+ }
+
+ environment {
+ OBJECT_KEY_PREFIX = "zentao/"
+ QINIU_ACCESS_KEY = credentials('qiniu-upload-ak')
+ QINIU_SECRET_KEY = credentials('qiniu-upload-sk')
+ }
+
+ steps {
+ sh 'ls -l ${OUTPUT_PKG_PATH}'
+ container('jnlp') {
+ sh 'qshell account $QINIU_ACCESS_KEY $QINIU_SECRET_KEY uploader'
+ sh 'qshell qupload2 --bucket $QINIU_BUCKET --overwrite --src-dir $OUTPUT_PKG_PATH --key-prefix $OBJECT_KEY_PREFIX'
+ }
+ }
+ }
+
+ } // end stages
+ } // end stage frame
+
+ } // End matrix stages
+ } // End matrix
+
+ } // End Merge and Upload Max
+
+ stage("Notice ZIP") {
+ when { environment name:'PUBLISH_ZIP', value:'true' }
+ steps {
+ checkout scmGit(branches: [[name: "master"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/devops/zentao-package.git']]
+ )
+ container('xuanimbot') {
+ sh './gen_build_report.sh > success.md'
+ }
+ script {
+ job.unStashMsgHeader()
+ ximNotify(title: "禅道源码包构建成功", contentFile: "success.md", contentHeaderFile: job.headerFileName())
+ }
+ }
+ }
+
+ stage("Zbox") {
+ when { environment name:'PUBLISH_ZBOX', value:'true' }
+
+ environment {
+ // printf "$PKG_URL_FORMATTER" pmsPack ZenTaoPMS 18.5 ZenTaoPMS-18.5-php8.1.zip
+ PKG_URL_FORMATTER = """${sh(
+ returnStdout: true,
+ script: "echo ${ARTIFACT_PROTOCOL}://${ARTIFACT_HOST}/repository/${ARTIFACT_REPOSITORY}/zentao/%s/`echo ${ARTIFACT_SUBGROUP} | tr . /`/%s/%s/%s"
+ ).trim()}"""
+ OBJECT_KEY_PREFIX = "zentao/"
+ QINIU_ACCESS_KEY = credentials('qiniu-upload-ak')
+ QINIU_SECRET_KEY = credentials('qiniu-upload-sk')
+ }
+
+ stages {
+ stage("Package") {
+ parallel {
+ stage("Zbox win") {
+ agent {
+ kubernetes {
+ yamlFile 'misc/ci/publish-zbox.yaml'
+ }
+ }
+
+ stages() {
+ stage("Prepare") {
+ steps {
+ checkout scmGit(branches: [[name: "main"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/devops/zbox-builder.git']]
+ )
+ }
+ }
+
+ stage("Build") {
+ steps {
+ script {
+ buildZboxWin.doBuild()
+ buildZboxWin.uploadInternal()
+ }
+ }
+ }
+
+ stage("Upload Qiniu") {
+ when {
+ environment name: 'BUILD_KIND', value: 'release'
+ }
+
+ steps {
+ container('docker') {
+ sh 'mkdir -pv ./release/upload && cd ./release/upload && mkdir $PMS_VERSION $BIZ_VERSION $MAX_VERSION $IPD_VERSION'
+ sh "mv `find release/zh-cn/$PMS_VERSION release/en/$PMS_VERSION -type f -name 'ZenTao*.exe'` release/upload/$PMS_VERSION"
+ sh "mv `find release/zh-cn/$BIZ_VERSION release/en/$BIZ_VERSION -type f -name 'ZenTao*.exe'` release/upload/$BIZ_VERSION"
+ sh "mv `find release/zh-cn/$MAX_VERSION release/en/$MAX_VERSION -type f -name 'ZenTao*.exe'` release/upload/$MAX_VERSION"
+ sh "mv `find release/zh-cn/$IPD_VERSION release/en/$IPD_VERSION -type f -name 'ZenTao*.exe'` release/upload/$IPD_VERSION"
+ }
+ container('jnlp') {
+ sh 'qshell account $QINIU_ACCESS_KEY $QINIU_SECRET_KEY uploader'
+ sh 'qshell qupload2 --bucket $QINIU_BUCKET --overwrite --src-dir ./release/upload --key-prefix $OBJECT_KEY_PREFIX'
+ }
+ }
+ } // End Upload Qiniu
+ }
+ } // End Zbox win
+
+ stage("Zbox linux") {
+ agent {
+ kubernetes {
+ yamlFile 'misc/ci/publish-zbox.yaml'
+ }
+ }
+
+ stages() {
+ stage("Prepare") {
+ steps {
+ checkout scmGit(branches: [[name: "main"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/devops/zbox-builder.git']]
+ )
+ }
+ }
+
+ stage("Build") {
+ steps {
+ script {
+ buildZboxLinux.doBuild()
+ buildZboxLinux.uploadInternal()
+ }
+ }
+ }
+
+ stage("Upload Qiniu") {
+ when {
+ environment name: 'BUILD_KIND', value: 'release'
+ }
+
+ steps {
+ container('docker') {
+ sh 'mkdir -pv ./release/upload && cd ./release/upload && mkdir $PMS_VERSION $BIZ_VERSION $MAX_VERSION $IPD_VERSION'
+ sh "mv `find release/zh-cn/$PMS_VERSION release/en/$PMS_VERSION -type f -name 'ZenTao*'` release/upload/$PMS_VERSION"
+ sh "mv `find release/zh-cn/$BIZ_VERSION release/en/$BIZ_VERSION -type f -name 'ZenTao*'` release/upload/$BIZ_VERSION"
+ sh "mv `find release/zh-cn/$MAX_VERSION release/en/$MAX_VERSION -type f -name 'ZenTao*'` release/upload/$MAX_VERSION"
+ sh "mv `find release/zh-cn/$IPD_VERSION release/en/$IPD_VERSION -type f -name 'ZenTao*'` release/upload/$IPD_VERSION"
+ }
+ container('jnlp') {
+ sh 'qshell account $QINIU_ACCESS_KEY $QINIU_SECRET_KEY uploader'
+ sh 'qshell qupload2 --bucket $QINIU_BUCKET --overwrite --src-dir ./release/upload --key-prefix $OBJECT_KEY_PREFIX'
+ }
+ }
+ } // End Upload Qiniu
+ }
+ } // End Zbox linux
+
+ } // End parallel
+ }
+
+ stage("Notice zbox") {
+ steps {
+ checkout scmGit(branches: [[name: "main"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/devops/zbox-builder.git']]
+ )
+ sh 'script/lib/gen_report.sh > zbox-success.md'
+ script {
+ job.unStashMsgHeader()
+ ximNotify(title: "禅道一键安装包构建成功", contentFile: "zbox-success.md", contentHeaderFile: job.headerFileName())
+ }
+ }
+ }
+ }
+
+ } // End Zbox
+
+ stage("Docker Image") {
+ when {
+ environment name:'PUBLISH_IMAGE', value:'true'
+ }
+
+ agent {
+ kubernetes {
+ inheritFrom "dind xuanim"
+ yamlFile 'misc/ci/publish-image.yaml'
+ }
+ }
+
+ environment {
+ REGISTRY_HOST="hub.zentao.net"
+ CI_BUILD_PUBLIC_IMAGE="true"
+ PKG_URL_FORMATTER = """${sh(
+ returnStdout: true,
+ script: "echo ${ARTIFACT_PROTOCOL}://${ARTIFACT_HOST}/repository/${ARTIFACT_REPOSITORY}/zentao/%s/`echo ${ARTIFACT_SUBGROUP} | tr . /`/%s/%s/%s"
+ ).trim()}"""
+ }
+
+ steps {
+ checkout scmGit(branches: [[name: "master"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/app/zentao.git']]
+ )
+ script {
+ dockerBuildx(host=env.REGISTRY_HOST, credentialsId=env.DOCKER_CREDENTIALS_ID) {
+ stage("prepare") {
+ sh "apk --no-cache add make bash jq git tzdata"
+ sh "make markdown-init"
+ }
+ stage("docker pms") {
+ sh 'make build'
+ }
+ stage("docker biz") {
+ sh 'make build-biz'
+ sh 'make build-biz-k8s'
+ }
+ stage("docker max") {
+ sh 'make build-max'
+ sh 'make build-max-k8s'
+ }
+ stage("docker ipd") {
+ sh 'make build-ipd'
+ sh 'make build-ipd-k8s'
+ }
+ sh 'make markdown-render > ./report.md'
+ }
+ }
+ script {
+ job.unStashMsgHeader()
+ ximNotify(title: "禅道镜像构建成功", contentFile: "report.md", contentHeaderFile: job.headerFileName())
+ }
+ }
+
+ } // End Docker Image
+
+ stage("Upload rongpm") {
+ agent {
+ kubernetes {
+ inheritFrom "zentao-package xuanim"
+ yamlFile 'misc/ci/normal.yaml'
+ }
+ }
+ when {
+ allOf {
+ environment name:'PUBLISH_ZIP', value:'true'
+ environment name:'BUILD_KIND', value:'release'
+ }
+
+ beforeAgent true
+ }
+
+ environment {
+ OBJECT_KEY_PREFIX = "zentao/"
+ QINIU_ACCESS_KEY = credentials('qiniu-upload-ak')
+ QINIU_SECRET_KEY = credentials('qiniu-upload-sk')
+ }
+
+ steps {
+ script {
+ checkout scmGit(branches: [[name: "master"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/web/rongpm.git']]
+ )
+
+ container('package') {
+ sh 'mkdir -p output/rongpm/${PMS_VERSION}'
+ sh 'cd output/rongpm/${PMS_VERSION} && php7.2 $WORKSPACE/system/bin/buildpractice.php && rm -rf rongpm'
+ }
+
+ container('jnlp') {
+ sh 'qshell account $QINIU_ACCESS_KEY $QINIU_SECRET_KEY uploader'
+ sh 'qshell qupload2 --bucket $QINIU_BUCKET --overwrite --src-dir ./output --key-prefix $OBJECT_KEY_PREFIX'
+ }
+ }
+ }
+ } // End Upload rongpm
+
+ }
+
+
+ } // end publish
+
+ }
+
+ post {
+ failure {
+ ximNotify(title: "禅道构建失败", content: "请点击查看详情")
+ }
+ }
+
+}
+
+
diff --git a/misc/ci/Jenkinsfile.devops b/misc/ci/Jenkinsfile.devops
new file mode 100644
index 0000000000..40c01775c3
--- /dev/null
+++ b/misc/ci/Jenkinsfile.devops
@@ -0,0 +1,464 @@
+pipeline {
+ agent {
+ kubernetes {
+ inheritFrom "xuanim"
+ yamlFile 'misc/ci/normal.yaml'
+ }
+ }
+
+ options {
+ skipDefaultCheckout()
+ }
+
+ environment {
+ TZ="Asia/Shanghai"
+
+ ZENTAO_RELEASE_PATH = "${WORKSPACE}/release"
+ XUANXUAN_SRC_PATH = "${WORKSPACE}/xuansrc"
+ SRC_ZDOO_PATH = "${WORKSPACE}/zdoo"
+ SRC_ZDOOEXT_PATH = "${WORKSPACE}/zdooext"
+ SRC_ZENTAOEXT_PATH = "${WORKSPACE}/zentaoext"
+
+ MIDDLE_IMAGE_REPO = "hub.qc.oop.cc/zentao-package-ext"
+ MIDDLE_IMAGE_TAG = """${sh(
+ returnStdout: true,
+ script: 'date +%Y%m%d%H%M-${BUILD_ID}'
+ ).trim()}"""
+
+ MIRROR = "true"
+
+ BUILD_BASIC = "true"
+ PUBLISH_ZIP = "true"
+ PUBLISH_IMAGE = "true"
+ PUBLISH_ZBOX = "true"
+
+ // set to blank for auto-detect from ci.json
+ DOWNGRADE_ENABLED = "false"
+ DOWNGRADE_VERSIONS = ""
+ }
+
+ stages {
+
+ stage("Basic Build") {
+
+ agent {
+ kubernetes {
+ inheritFrom "zentao-package build-docker xuanim"
+ yamlFile 'misc/ci/basic-build.yaml'
+ }
+ }
+
+ stages {
+ stage("Pull") {
+ steps {
+ checkout scm
+ script {
+ env.XUANVERSION = sh(returnStdout: true,script: 'jq -r .pkg.xuanxuan.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_GIT_REPO = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitRepo < ci.json').trim()
+ env.ZDOO_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdoo.gitVersion < ci.json').trim()
+ env.ZDOOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdooext.gitVersion < ci.json').trim()
+
+ }
+
+ dir('xuansrc') {
+ checkout scmGit(branches: [[name: "${env.XUANVERSION}"]],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/xuanxuan.git']]
+ )
+ }
+
+ dir('zdoo') {
+ checkout scmGit(branches: [[name: "${env.ZDOO_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdoo.git']]
+ )
+ }
+
+ dir('zdooext') {
+ checkout scmGit(branches: [[name: "${env.ZDOOEXT_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdooext.git']]
+ )
+ }
+
+ dir('zentaoext') {
+ checkout scmGit(branches: [[name: "${env.ZENTAOEXT_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: "${env.ZENTAOEXT_GIT_REPO}"]]
+ )
+ }
+
+ }
+ }
+
+ stage("Setup Global Env") {
+ steps {
+ script {
+ env.GIT_URL = sh(returnStdout: true, script: 'git config --get remote.origin.url').trim()
+ env.GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
+ env.GIT_TAG_BUILD_TYPE = 'snapshot'
+ env.GIT_TAG_BUILD_GROUP = sh(returnStdout: true, script: 'echo `date +%Y%m%d`.devops').trim()
+ env.GIT_TAGGER_NAME = sh(returnStdout: true, script: 'git show -s --format=%an').trim()
+
+ env.CI_PUBLIC_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.public.namespace.' + env.GIT_TAG_BUILD_TYPE + ' < ci.json').trim()
+ env.CI_INTERNAL_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.internal.namespace.' + env.GIT_TAG_BUILD_TYPE + ' < ci.json').trim()
+
+ // def ximUsers = sh(returnStdout: true,script: 'jq -r .notice.users < ci.json').trim()
+ env.XIM_USERS = 'qishiyao,zhaoke,zhengyuansheng'
+ // env.XIM_GROUPS = sh(returnStdout: true,script: 'jq -r .notice.groups < ci.json').trim()
+
+ env.PMS_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/VERSION').trim()
+ env.BIZ_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/BIZVERSION').trim()
+ env.MAX_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/MAXVERSION').trim()
+ env.IPD_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/IPDVERSION').trim()
+
+ env.CI_DOWNGRADE_ENABLED = sh(returnStdout: true, script: 'test -n "${DOWNGRADE_ENABLED}" && echo ${DOWNGRADE_ENABLED} || (jq -r .downgrade.enabled < ci.json)').trim()
+ env.QINIU_BUCKET = sh(returnStdout: true, script: 'jq -r .upload.bucket < ci.json').trim()
+ env.ARTIFACT_REPOSITORY = sh(returnStdout: true, script: 'misc/parse_tag.sh $TAG_NAME type | grep release >/dev/null && echo easycorp || echo easycorp-snapshot').trim()
+ env.ARTIFACT_HOST = "nexus.qc.oop.cc"
+ env.ARTIFACT_PROTOCOL = "https"
+ env.ARTIFACT_CRED_ID = "nexus-jenkins"
+ }
+
+ }
+ }
+
+ stage("Build") {
+ when {
+ environment name:'BUILD_BASIC', value:'true'
+ }
+ stages {
+ stage("make ciCommon") {
+ steps {
+ container('xuanimbot') {
+ sh 'git config --global --add safe.directory $(pwd)'
+ sh '/usr/local/bin/xuanimbot --title "`echo -n 5byA5aeL5p6E5bu656aF6YGT | base64 --decode`" --url "${BUILD_URL}" --content "Build by Tag ${TAG_NAME}" --debug --custom'
+ }
+ withCredentials([gitUsernamePassword(credentialsId: 'git-zcorp-cc-jenkins-bot-http',gitToolName: 'git-tool')]) {
+ container('package') {
+ sh 'mkdir ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
+ sh 'git config --global pull.ff only'
+ sh 'cp -av ${ZENTAO_BUILD_PATH}/adminer www/'
+ sh 'pwd && ls -l && make ciCommon'
+ sh 'ls -l ${ZENTAO_RELEASE_PATH}'
+ }
+ }
+ }
+ }
+
+ stage("zentaoext") {
+ steps {
+ container('package') {
+ sh 'cd $SRC_ZENTAOEXT_PATH && make'
+ sh 'cp ${ZENTAO_BUILD_PATH}/zentao*.zip ./'
+ sh 'cp ${ZENTAO_BUILD_PATH}/docker/Dockerfile.release.ext ./Dockerfile.release.ext'
+ }
+ container('docker') {
+ sh 'docker build --pull -t ${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG} -f Dockerfile.release.ext ${ZENTAO_RELEASE_PATH}'
+ sh 'docker push ${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG}'
+ }
+ }
+ }
+
+ }
+ } // End Build
+ }
+ }
+
+ stage("Publish") {
+ environment {
+ OUTPUT_PKG_PATH = "${ZENTAO_RELEASE_PATH}/output"
+ }
+
+ stages {
+ stage("Merge and Upload") {
+ when {
+ environment name:'PUBLISH_ZIP', value:'true'
+ }
+ matrix {
+ agent {
+ kubernetes {
+ containerTemplate {
+ name "package"
+ image "${MIDDLE_IMAGE_REPO}:${MIDDLE_IMAGE_TAG}"
+ command "sleep"
+ args "99d"
+ }
+ }
+ }
+ options {
+ skipDefaultCheckout()
+ }
+
+ axes {
+ axis {
+ name "ZLANG"
+ values "cn"
+ }
+ axis {
+ name "PHPVERSION"
+ values "k8s.php8.1"
+ }
+ }
+ excludes {
+ exclude {
+ axis {
+ name 'ZLANG'
+ values 'en'
+ }
+ axis {
+ name "PHPVERSION"
+ values "k8s.php7.2_7.4", "k8s.php8.1"
+ }
+ }
+ }
+
+ stages {
+
+ stage("ZIP") {
+ environment {
+ ARTIFACT_NAME = """${sh(
+ returnStdout: true,
+ script: 'test ${ZLANG} = cn && echo -n ZenTaoPMS || echo -n ZenTaoALM'
+ ).trim()}"""
+ INT_FLAG = """${sh(
+ returnStdout: true,
+ script: 'test ${ZLANG} = cn && echo -n "int." || echo -n ""'
+ ).trim()}"""
+ }
+
+ stages {
+ stage("package zip") {
+ steps{
+ echo "${env.ZLANG} <=> ${env.PHPVERSION}"
+ container('package') {
+ sh 'mkdir $ZENTAO_RELEASE_PATH'
+ sh '${ZENTAO_BUILD_PATH}/package.sh zip'
+ sh 'mkdir $OUTPUT_PKG_PATH'
+ }
+ }
+ }
+
+ stage("upload zip") {
+ steps {
+ nexusArtifactUploader(
+ nexusVersion: 'nexus3',
+ protocol: env.ARTIFACT_PROTOCOL,
+ nexusUrl: env.ARTIFACT_HOST,
+ groupId: 'zentao.pmsPack' + '.' + env.GIT_TAG_BUILD_GROUP,
+ version: env.PMS_VERSION,
+ repository: env.ARTIFACT_REPOSITORY,
+ credentialsId: env.ARTIFACT_CRED_ID,
+ artifacts: [
+ [artifactId: env.ARTIFACT_NAME,
+ classifier: env.PHPVERSION,
+ file: env.ZENTAO_RELEASE_PATH + '/base.zip',
+ type: 'zip']
+ ]
+ )
+
+ sh 'mkdir ${OUTPUT_PKG_PATH}/${PMS_VERSION}'
+ sh 'cp ${ZENTAO_RELEASE_PATH}/base.zip ${OUTPUT_PKG_PATH}/${PMS_VERSION}/${ARTIFACT_NAME}-${PMS_VERSION}-${PHPVERSION}.zip'
+
+ script {
+ def pkgVersionMap = [
+ "biz": ["bizPack", env.BIZ_VERSION],
+ "max": ["maxPack", env.MAX_VERSION],
+ "ipd": ["ipdPack", env.IPD_VERSION]
+ ]
+
+ for (entry in pkgVersionMap.entrySet()) {
+ echo "set ExtName to ${entry.key}"
+ def ExtName = entry.key
+ echo "set SubGroup to ${entry.value[0]}"
+ def SubGroup = entry.value[0]
+ echo "set Zversion to ${entry.value[1]}"
+ def Zversion = entry.value[1]
+
+ nexusArtifactUploader(
+ nexusVersion: 'nexus3',
+ protocol: env.ARTIFACT_PROTOCOL,
+ nexusUrl: env.ARTIFACT_HOST,
+ groupId: 'zentao.' + SubGroup + '.' + env.GIT_TAG_BUILD_GROUP,
+ version: Zversion,
+ repository: env.ARTIFACT_REPOSITORY,
+ credentialsId: env.ARTIFACT_CRED_ID,
+ artifacts: [
+ [artifactId: env.ARTIFACT_NAME,
+ classifier: env.PHPVERSION,
+ file: env.ZENTAO_RELEASE_PATH + '/' + ExtName + '.zip',
+ type: 'zip'],
+ [artifactId: env.ARTIFACT_NAME,
+ classifier: 'update.' + env.PHPVERSION,
+ file: env.ZENTAO_RELEASE_PATH + '/' + ExtName + '.update.zip',
+ type: 'zip']
+ ]
+ )
+ sh 'mkdir ${OUTPUT_PKG_PATH}/' + Zversion
+ def moveCmd1 = String.format('mv ${ZENTAO_RELEASE_PATH}/%s.zip ${OUTPUT_PKG_PATH}/%s/${ARTIFACT_NAME}-%s-${PHPVERSION}.zip', ExtName, Zversion, Zversion)
+ sh moveCmd1
+
+ def moveCmd2 = String.format('mv ${ZENTAO_RELEASE_PATH}/%s.update.zip ${OUTPUT_PKG_PATH}/%s/${ARTIFACT_NAME}-%s-update.${PHPVERSION}.zip', ExtName, Zversion, Zversion)
+ sh moveCmd2
+
+ } // End for loop
+ }
+
+
+ }
+ } // End upload zip
+
+ stage("syspack") {
+ when {
+ // buildingTag()
+ environment name: 'GIT_TAG_BUILD_TYPE', value: 'release'
+ }
+ steps{
+ sh 'env | grep GIT_TAG'
+ container('package') {
+ sh '${ZENTAO_BUILD_PATH}/package.sh deb'
+ sh '${ZENTAO_BUILD_PATH}/package.sh rpm'
+ }
+ }
+ }
+
+ stage("upload syspack") {
+ when {
+ environment name: 'GIT_TAG_BUILD_TYPE', value: 'release'
+ }
+
+ steps {
+ sh 'mv ${ZENTAO_RELEASE_PATH}/zentao.rpm ${OUTPUT_PKG_PATH}/${PMS_VERSION}/${ARTIFACT_NAME}-${PMS_VERSION}-${PHPVERSION}-1.noarch.rpm'
+ sh 'mv ${ZENTAO_RELEASE_PATH}/zentao.deb ${OUTPUT_PKG_PATH}/${PMS_VERSION}/${ARTIFACT_NAME}_${PMS_VERSION}-${PHPVERSION}-1_all.deb'
+ }
+ } // End upload syspack cn
+
+ stage("Upload Qiniu") {
+ when {
+ environment name: 'GIT_TAG_BUILD_TYPE', value: 'release'
+ }
+
+ environment {
+ OBJECT_KEY_PREFIX = "zentao/"
+ QINIU_ACCESS_KEY = credentials('qiniu-upload-ak')
+ QINIU_SECRET_KEY = credentials('qiniu-upload-sk')
+ }
+
+ steps {
+ sh 'ls -l ${OUTPUT_PKG_PATH}'
+ container('jnlp') {
+ sh 'qshell account $QINIU_ACCESS_KEY $QINIU_SECRET_KEY uploader'
+ sh 'qshell qupload2 --bucket $QINIU_BUCKET --overwrite --src-dir $OUTPUT_PKG_PATH --key-prefix $OBJECT_KEY_PREFIX'
+ }
+ }
+ }
+
+ } // end stages
+ } // end stage frame
+
+ } // End matrix stages
+ } // End matrix
+
+ } // End Merge and Upload Max
+
+ stage("Notice ZIP") {
+ when {
+ environment name:'PUBLISH_ZIP', value:'true'
+ }
+ steps {
+ checkout scmGit(branches: [[name: "master"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/devops/zentao-package.git']]
+ )
+ container('xuanimbot') {
+ sh 'env | grep GIT'
+ sh 'git config --global --add safe.directory $(pwd)'
+ sh './gen_build_report.sh > success.md'
+ sh '/usr/local/bin/xuanimbot --title "`echo -n 56aF6YGT5rqQ56CB5YyF5p6E5bu65oiQ5Yqf | base64 --decode`" --url "${RUN_DISPLAY_URL}" --content-file success.md --debug --custom'
+ }
+ }
+ }
+
+ stage("Docker Image") {
+ when {
+ environment name:'PUBLISH_IMAGE', value:'true'
+ }
+
+ agent {
+ kubernetes {
+ inheritFrom "dind xuanim"
+ yamlFile 'misc/ci/publish-image.yaml'
+ }
+ }
+
+ environment {
+ REGISTRY_HOST="hub.zentao.net"
+ CI_BUILD_PUBLIC_IMAGE="true"
+ CI_PHP_VER="8.1.23"
+ }
+
+ stages() {
+ stage("prepare") {
+ steps {
+ sh 'env'
+ checkout scmGit(branches: [[name: "master"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/app/zentao.git']]
+ )
+ container('docker') {
+ sh "sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories"
+ sh "apk --no-cache add make bash jq git tzdata"
+ sh "make markdown-init"
+ }
+ }
+ }
+
+ stage("docker ipd") {
+ environment {
+ ZENTAO_URL = """${sh(
+ returnStdout: true,
+ script: "echo ${ARTIFACT_PROTOCOL}://${ARTIFACT_HOST}/repository/${ARTIFACT_REPOSITORY}/zentao/ipdPack/`echo ${GIT_TAG_BUILD_GROUP} | tr . /`/ZenTaoPMS"
+ ).trim()}"""
+ }
+ steps {
+ container('docker') {
+ withDockerRegistry(credentialsId: 'hub-qucheng-push', url: 'https://' + env.REGISTRY_HOST) {
+ sh "docker buildx create --name mybuilder --driver docker-container --bootstrap"
+ sh "docker buildx use mybuilder"
+ sh 'make build-ipd-k8s'
+ }
+ }
+ }
+ }
+
+ stage("Notice Image") {
+ steps {
+ container('docker') {
+ sh 'make markdown-render > ./report.md'
+ }
+ container('xuanimbot') {
+ sh 'git config --global --add safe.directory $(pwd)'
+ sh '/usr/local/bin/xuanimbot --title "`echo -n 56aF6YGT6ZWc5YOP5p6E5bu65oiQ5Yqf | base64 --decode`" --url "${RUN_DISPLAY_URL}" --content-file ./report.md --debug --custom'
+ }
+ }
+ }
+
+ }
+ } // End Docker Image
+
+ }
+
+ post {
+ failure {
+ container('xuanimbot') {
+ sh 'git config --global --add safe.directory $(pwd)'
+ sh '/usr/local/bin/xuanimbot --title "zentaopms build failed" --url "${BUILD_URL}" --content "" --debug --custom'
+ }
+ }
+ }
+ } // end publish
+
+ }
+
+}
+
diff --git a/misc/ci/Jenkinsfile.downgrade.18x b/misc/ci/Jenkinsfile.downgrade.18x
new file mode 100755
index 0000000000..2d772d6b02
--- /dev/null
+++ b/misc/ci/Jenkinsfile.downgrade.18x
@@ -0,0 +1,152 @@
+pipeline {
+ agent {
+ kubernetes {
+ inheritFrom "xuanim"
+ yamlFile 'misc/ci/normal.yaml'
+ }
+ }
+
+ options {
+ skipDefaultCheckout()
+ }
+
+ environment {
+ TZ="Asia/Shanghai"
+
+ ZENTAO_RELEASE_PATH = "${WORKSPACE}/release"
+ XUANXUAN_SRC_PATH = "${WORKSPACE}/xuansrc"
+ SRC_ZDOO_PATH = "${WORKSPACE}/zdoo"
+ SRC_ZDOOEXT_PATH = "${WORKSPACE}/zdooext"
+ SRC_ZENTAOEXT_PATH = "${WORKSPACE}/zentaoext"
+
+ // set to blank for auto-detect from ci.json
+ DOWNGRADE_ENABLED = "false"
+ DOWNGRADE_VERSIONS = "7.2,7.1,7.0,5.4"
+ }
+
+ stages {
+
+ stage("Test Code") {
+
+ agent {
+ kubernetes {
+ inheritFrom "zentao-package build-docker xuanim"
+ yamlFile 'misc/ci/basic-build.yaml'
+ }
+ }
+
+ stages {
+ stage("Pull") {
+ steps {
+ checkout scm
+ script {
+ env.XUANVERSION = sh(returnStdout: true,script: 'jq -r .pkg.xuanxuan.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitVersion < ci.json').trim()
+ env.ZENTAOEXT_GIT_REPO = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitRepo < ci.json').trim()
+ env.ZDOO_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdoo.gitVersion < ci.json').trim()
+ env.ZDOOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdooext.gitVersion < ci.json').trim()
+ }
+
+ dir('xuansrc') {
+ checkout scmGit(branches: [[name: "${env.XUANVERSION}"]],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/xuanxuan.git']]
+ )
+ }
+
+ dir('zdoo') {
+ checkout scmGit(branches: [[name: "${env.ZDOO_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdoo.git']]
+ )
+ }
+
+ dir('zdooext') {
+ checkout scmGit(branches: [[name: "${env.ZDOOEXT_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdooext.git']]
+ )
+ }
+
+ dir('zentaoext') {
+ checkout scmGit(branches: [[name: "${env.ZENTAOEXT_VERSION}"]],
+ extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
+ userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: "${env.ZENTAOEXT_GIT_REPO}"]]
+ )
+ }
+
+ }
+ }
+
+ stage("Setup Global Env") {
+ steps {
+ script {
+ def ximUsers = sh(returnStdout: true,script: 'jq -r .notice.users < ci.json').trim()
+ env.XIM_USERS = "qishiyao"
+ env.XIM_GROUPS = "31a0008b-6e3e-4b7f-9b7b-396a46b1f8f4"
+
+ env.PMS_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/VERSION').trim()
+ env.BIZ_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/BIZVERSION').trim()
+ env.MAX_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/MAXVERSION').trim()
+ env.IPD_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/IPDVERSION').trim()
+
+ }
+
+ }
+ }
+
+ stage("Downgrade") {
+ stages {
+ stage("make Package") {
+ steps {
+ withCredentials([gitUsernamePassword(credentialsId: 'git-zcorp-cc-jenkins-bot-http',gitToolName: 'git-tool')]) {
+ container('package') {
+ sh 'mkdir ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
+ sh 'git config --global pull.ff only'
+ sh 'pwd && ls -l && make ciCommon'
+ sh 'ls -l ${ZENTAO_RELEASE_PATH}'
+ }
+ }
+
+ }
+ }
+
+ stage("test downgrade") {
+ steps {
+ container('package') {
+ sh 'unzip -q ZenTaoPMS.${PMS_VERSION}.zip'
+ sh 'ls -l zentaopms'
+ sh './misc/downgrade.sh -p "$DOWNGRADE_VERSIONS" -r zentaopms -i -s -o "$ZENTAO_RELEASE_PATH" framework lib module/*'
+ }
+
+ container('package') {
+ sh 'mkdir /tmp/publishHtml'
+ sh 'test -f ${ZENTAO_RELEASE_PATH}/downgradeReport.html && cp ${ZENTAO_RELEASE_PATH}/downgradeReport.html /tmp/publishHtml/ || echo "No syntax error"'
+ }
+
+ publishHTML([
+ allowMissing: true,
+ alwaysLinkToLastBuild: false,
+ keepAll: true,
+ reportDir: '/tmp/publishHtml',
+ reportFiles: 'downgradeReport.html',
+ reportName: 'DowngradeReport'
+ ])
+
+ container('xuanimbot') {
+ sh 'git config --global --add safe.directory $(pwd)'
+ sh 'test -f $ZENTAO_RELEASE_PATH/downgradeReport.html && /usr/local/bin/xuanimbot --title "`echo -n 6ZmN57qn5aSx6LSl | base64 --decode`" --url "${RUN_DISPLAY_URL}" --content "[PHP Syntax Report]($BUILD_URL/DowngradeReport/)" --debug --custom || echo "No syntax found"'
+ }
+
+ }
+ }
+
+ }
+ } // End Downgrade
+ }
+ }
+
+ }
+
+}
+
+
diff --git a/misc/ci/basic-build.yaml b/misc/ci/basic-build.yaml
new file mode 100644
index 0000000000..8e227be893
--- /dev/null
+++ b/misc/ci/basic-build.yaml
@@ -0,0 +1,21 @@
+spec:
+ affinity:
+ nodeAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: Gt
+ values:
+ - "7"
+ containers:
+ - name: docker
+ volumeMounts:
+ - mountPath: /var/run/docker.sock
+ name: docker-socket
+ volumes:
+ - name: docker-socket
+ hostPath:
+ path: /var/run/docker.sock
+ type: Socket
diff --git a/misc/ci/normal.yaml b/misc/ci/normal.yaml
new file mode 100644
index 0000000000..ac8dc8a303
--- /dev/null
+++ b/misc/ci/normal.yaml
@@ -0,0 +1,17 @@
+spec:
+ affinity:
+ nodeAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: Lt
+ values:
+ - "8"
+ - weight: 50
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: DoesNotExist
+
diff --git a/misc/ci/publish-image.yaml b/misc/ci/publish-image.yaml
new file mode 100644
index 0000000000..8e227be893
--- /dev/null
+++ b/misc/ci/publish-image.yaml
@@ -0,0 +1,21 @@
+spec:
+ affinity:
+ nodeAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: Gt
+ values:
+ - "7"
+ containers:
+ - name: docker
+ volumeMounts:
+ - mountPath: /var/run/docker.sock
+ name: docker-socket
+ volumes:
+ - name: docker-socket
+ hostPath:
+ path: /var/run/docker.sock
+ type: Socket
diff --git a/misc/ci/publish-zbox.yaml b/misc/ci/publish-zbox.yaml
new file mode 100644
index 0000000000..131938b2da
--- /dev/null
+++ b/misc/ci/publish-zbox.yaml
@@ -0,0 +1,36 @@
+metadata:
+ labels:
+ ci-cpu-level: high
+spec:
+ affinity:
+ nodeAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: Gt
+ values:
+ - "7"
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: ci-cpu-level
+ operator: In
+ values:
+ - high
+ topologyKey: "kubernetes.io/hostname"
+ containers:
+ - name: docker
+ image: docker:23.0.6-dind-alpine3.17
+ command: ["sleep"]
+ args: ["99d"]
+ volumeMounts:
+ - mountPath: /var/run/docker.sock
+ name: docker-socket
+ volumes:
+ - name: docker-socket
+ hostPath:
+ path: /var/run/docker.sock
+ type: Socket
diff --git a/misc/ci/publish-zip.yaml b/misc/ci/publish-zip.yaml
new file mode 100644
index 0000000000..e273ff39ee
--- /dev/null
+++ b/misc/ci/publish-zip.yaml
@@ -0,0 +1,11 @@
+spec:
+ affinity:
+ nodeAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ preference:
+ matchExpressions:
+ - key: easycorp.io/cpu-spec
+ operator: Gt
+ values:
+ - "7"
diff --git a/misc/composer.json b/misc/composer.json
new file mode 100644
index 0000000000..caaeaf154d
--- /dev/null
+++ b/misc/composer.json
@@ -0,0 +1,15 @@
+{
+ "require-dev": {
+ "rector/rector": "0.15.*"
+ },
+ "scripts": {
+ "rector": [
+ "Composer\\Config::disableProcessTimeout",
+ "rector process --no-diffs --no-progress-bar"
+ ],
+ "rector-nocache": [
+ "Composer\\Config::disableProcessTimeout",
+ "rector process --no-diffs --no-progress-bar --clear-cache"
+ ]
+ }
+}
diff --git a/misc/downgrade-report-tpl.html b/misc/downgrade-report-tpl.html
new file mode 100644
index 0000000000..06a2c9761e
--- /dev/null
+++ b/misc/downgrade-report-tpl.html
@@ -0,0 +1,116 @@
+
+
+
+ PHP Syntax Errors
+
+
+
+ /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
+}
+
+syntaxInitReport() {
+ echo '{"versions": {}}' > "$statisticDataFile"
+}
+
+syntaxAddErrRecord() {
+ phpVersion="$1"
+ phpFilePath="$2"
+
+ phpCli=$(which php"${phpVersion}")
+ listKey=".versions[\"$phpVersion\"]"
+
+ fileName=$(echo "$phpFilePath"| sed "s|${codeRootDir}/||")
+
+ lastDir=$PWD
+ cd "$codeRootDir" || exit 1
+
+ set +e
+ errMsg=$($phpCli -l "$fileName" | jq -Rs '.')
+ set -e
+
+ cd "$lastDir"
+
+ jq "$listKey += [\"$fileName\", $errMsg]" "$statisticDataFile" > "$statisticDataFile.tmp"
+ mv "$statisticDataFile.tmp" "$statisticDataFile"
+}
+
+preProcess() {
+ # 初始化语法错误记录
+ if [[ "$statisticSyntax" == "true" ]];then
+ syntaxInitReport
+ fi
+
+ # 执行 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
+}
+
+process() {
+ # 设置脚本启动时间
+ startPointTime=$(date +'%Y-%m-%d %H:%M:%S')
+
+ # 检查 -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
+}
+
+postProcess() {
+ if [[ "$statisticSyntax" == "true" ]];then
+ foundSyntaxErr="false"
+ for ver in $(jq -r '.versions | keys[]' "$statisticDataFile")
+ do
+ count=$(jq -r ".versions[\"$ver\"] | length" < "$statisticDataFile")
+ if [[ "$count" -gt 0 && "$foundSyntaxErr" != "true" ]];then
+ foundSyntaxErr=true
+ fi
+ done
+
+ if [[ "$foundSyntaxErr" == "true" ]];then
+ reportInsertLine=$(grep -n 'const jsonData' "$reportHtmlFile" | cut -d: -f1)
+ jq -c . "$statisticDataFile" | sed "${reportInsertLine}r /dev/stdin" "$reportHtmlFile" > "$outputDir/downgradeReport.html"
+ fi
+ fi
+}
+
+preProcess
+process
+postProcess
+
+# syntaxCheck "$phpVer" /code/module/pipeline/model.php
+# downGradeDir "$phpVer" "${downGradeDirs[@]}"
+# archiveChangedFiles "$t" "$codeRootDir"
diff --git a/misc/parse_tag.sh b/misc/parse_tag.sh
new file mode 100755
index 0000000000..eeda8c2095
--- /dev/null
+++ b/misc/parse_tag.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+TAG=$1
+FIELD=$2
+
+frameCount=`echo $TAG| grep -o '_' | wc -l`
+
+build_id=''
+
+if [ $frameCount -eq 3 ];then
+ build_id=`echo $TAG | cut -d _ -f 3`
+fi
+
+case $FIELD in
+ group)
+ case $frameCount in
+ 1)
+ echo release
+ ;;
+ *)
+ build_date=${TAG##*_}
+ if [ -n "$build_id" ];then
+ echo "$build_date.$build_id"
+ else
+ echo "$build_date.release"
+ fi
+ ;;
+ esac
+ ;;
+ type)
+ if [ -n "$build_id" ];then
+ echo snapshot
+ else
+ echo release
+ fi
+ ;;
+esac
diff --git a/misc/rector.php b/misc/rector.php
new file mode 100644
index 0000000000..3476a7a7c3
--- /dev/null
+++ b/misc/rector.php
@@ -0,0 +1,23 @@
+skip([
+ 'test/*',
+ ]);
+ $rectorConfig->phpVersion(constant(PhpVersion::class . '::PHP_' . $version));
+ $rectorConfig->sets([constant(DowngradeLevelSetList::class . '::DOWN_TO_PHP_' . $version)]);
+ // $rectorConfig->sets([constant(DowngradeSetList::class . '::PHP_' . $version)]);
+ $rectorConfig->cacheClass(FileCacheStorage::class);
+ $rectorConfig->cacheDirectory($cacheDir);
+};
+
+
+
+
+
diff --git a/misc/downgrade.sh b/misc/downgrade.sh
new file mode 100755
index 0000000000..b7f505f99a
--- /dev/null
+++ b/misc/downgrade.sh
@@ -0,0 +1,386 @@
+#!/bin/bash
+set -e
+
+phpVer="7.4"
+installRector="false"
+statisticSyntax="false"
+codeRootDir=$PWD
+composerDir="$(dirname "$(realpath "$0")")"
+outputDir="$PWD/tmp"
+reportHtmlFile="$PWD/misc/downgrade-report-tpl.html"
+
+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 指定要降级的代码根目录, 默认为当前目录
+ -s 校验语法时不中断, 生成统计报告
+ -o 降级补丁包输出目录
+EOF
+}
+
+# 处理命令行参数, 获取要降级的目录
+[ $# -eq 0 ] && helper
+while getopts "isr:p:o:" opt; do
+ case $opt in
+ "p")
+ phpVer=$OPTARG
+ ;;
+ "r")
+ codeRootDir=$(realpath "$OPTARG")
+ ;;
+ "i")
+ installRector="true"
+ ;;
+ "s")
+ statisticSyntax="true"
+ ;;
+ "o")
+ outputDir=$OPTARG
+ ;;
+ *)
+ helper
+ exit 1
+ ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+statisticDataFile="$outputDir/statistic.data.json"
+
+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=(
+lib/purifier/HTMLPurifier.autoload-legacy.php
+lib/sqlparser/vendor/
+lib/phpaes/phpseclib/
+)
+
+##################################
+# 在非容器环境,返回 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;iPHP Syntax Errors
+ +
+
+
+