因工作需要,用户提出希望可以做ccs项目的持续集成,及代码提交后能够自动编译并提交到svn。调研过jenkins之后发现重新手写更有性价比,所以肝了几晚终于搞出来了,现在分享出来。
先交代背景:
1. 代码分两部分,一部分在git上,一部分在svn上2. 希望git上提交的代码时和svn上提交代码时都触发持续集成。
实现功能:
-
git上提交代码时自动触发持续集成
-
svn上提交代码时,并在备注中以“编译”开头时触发持续集成
-
持续集成功能:
a. 将git上的代码复制到 svn的 编译目录(记为 X)中
b. 将svn的源码目录(记为S)复制到svn的编译目录X的子目录(X1)中
c. 执行ccs的编译命令,编译ccs项目,
d. 将编译出的结果文件分别复制到 svn的多个目录中,
e. 将编译结果文件提交到svn,备注日志中包括git上的版本信息、svn源码目录(S)的版本信息。
实现说明:
-
使用springboot 搭建一个web项目,并提供一个接口用户触发持续集成,记为接口X
-
在git配置webhook,在代码检入时调用接口X (下面的配置需要使用管理员的账号)
-
在svn中编写钩子函数,在备注信息以”编译“开头时,调用接口x
# 构造函数代码片段,此代码在svn的仓库目录下的hooks目中,文件名称为 post-commit 对的,没有后缀 COMMENT=$(svnlook log -r $REV $REPOS)if echo "$COMMENT" | grep -qE '^编译'; thenecho "提交日志以'编译'开头。" >> ${SVN_LOG_FILE_PATH}curl -X post -v http://xxxx/cicd/xxx #这个就是接口x的地址了
-
接口X的具体逻辑如下:
整体逻辑是:
a. 将git 和svn上的代码更新到本地
b. 将文件复制到指定目录中
c. 执行编译命令: 编译命令使用的是ccs的编译命令
d. 判断编译是否成功,成功的话则将编译结果复制到指定目录中
e. 获取源码目录的最新版本号及备注信息,并拼接成备注信息,将结果文件提交到svn上。
先将其关键代码展示:
// 操作git,使用的是org.eclipse.jgit 5.13.3.202401111512-r /*** 克隆仓库** @throws Exception*/public void cloneRep(boolean force) throws Exception {File targetDirectory = new File(getLocalPath());boolean exists = targetDirectory.exists();if (exists && force) {FileUtil.del(targetDirectory);} else if (exists) {return;}Git.cloneRepository().setURI(getRepUrl()).setBranch(getBranch()).setDirectory(targetDirectory).setCredentialsProvider(new UsernamePasswordCredentialsProvider(getUsername(), getPassword())).call();}/** 获取仓库版本 */public String getRepVersion(){File localFile = new File(getLocalPath());boolean exists = localFile.exists();if (!exists) {return "";}try (Git git = Git.open(localFile)) {final Iterable<RevCommit> revCommits = git.log().setMaxCount(1).call();final RevCommit revCommit = revCommits.iterator().next();final String commitDate = DateUtil.format(revCommit.getAuthorIdent().getWhen(), "yyyy-MM-dd HH:mm:ss");final String commitName = revCommit.getAuthorIdent().getName();return String.format("%s(%s)", commitName,commitDate);} catch (Exception e) {log.error(e.getMessage(), e);}return "";}/** 更新仓库 */public void updateRep(boolean force) throws Exception {File localFile = new File(getLocalPath());boolean exists = localFile.exists();if (!exists) {cloneRep(force);return;}try (Git git = Git.open(localFile)) {if (force) {// 撤销所有未提交的本地修改git.reset().setMode(ResetCommand.ResetType.HARD).call();// 删除未跟踪的文件和目录git.clean().setCleanDirectories(true) // 递归清理子目录.call();}// 设置凭据CredentialsProvider cp = new UsernamePasswordCredentialsProvider(getUsername(), getPassword());git.fetch().setCredentialsProvider(cp).call();git.pull().setRebase(true) // 默认情况下合并(merge),这里改为变基(rebase).setCredentialsProvider(cp).call();} catch (RepositoryNotFoundException e) {// 未找到仓库cloneRep(true);}}
// 操作 svn
static {DAVRepositoryFactory.setup();SVNRepositoryFactoryImpl.setup();FSRepositoryFactory.setup();}public void updateRep() throws Exception {updateRep(true);}public void updateRep(boolean force) throws Exception {log.info("updateRep");BasicAuthenticationManager authManager = new BasicAuthenticationManager(getUsername(), getPassword());SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(getRepUrl()));repository.setAuthenticationManager(authManager);File targetFile = new File(getLocalPath(), "\\");if (force && targetFile.exists()) {// 撤销本地修改SVNWCClient wcClient = SVNClientManager.newInstance(null, authManager).getWCClient();wcClient.doRevert(new File[]{targetFile}, SVNDepth.INFINITY, null);}// 检出SVNUpdateClient updateClient = SVNClientManager.newInstance(null, authManager).getUpdateClient();updateClient.doCheckout(repository.getLocation(), targetFile, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY, false);}public void commit(List<File> delFileList) throws Exception {commit("", delFileList);}public void commit(String commitMsg, List<File> delFileList) throws Exception {if (!isNeedCommit()) {log.info("不需要提交,直接跳过!");return;}BasicAuthenticationManager authManager = new BasicAuthenticationManager(getUsername(), getPassword());SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(getRepUrl()));repository.setAuthenticationManager(authManager);SVNCommitClient client = SVNClientManager.newInstance(null, authManager).getCommitClient();File[] pathsToCommit = {new File(getLocalPath())};List<SVNURL> delSvnUrlList = new ArrayList<>();if (delFileList != null && !delFileList.isEmpty()) {for (File file : delFileList) {SVNURL svnUrl = getSvnUrl(file);if (isURLExist(svnUrl)) {delSvnUrlList.add(svnUrl);} else {file.delete();}}}if (!delSvnUrlList.isEmpty()) {SVNURL[] array = delSvnUrlList.toArray(new SVNURL[0]);// 先把老的旧文件删除掉。client.doDelete(array, StrUtil.isBlank(commitMsg) ? getCommitMsg() : commitMsg);}// 添加新增加的文件SVNClientManager.newInstance(null, authManager).getWCClient().doAdd(pathsToCommit, true, true, true, SVNDepth.INFINITY, true, false, true);SVNCommitInfo commitInfo = client.doCommit(pathsToCommit, false,StrUtil.isBlank(commitMsg) ? getCommitMsg() : commitMsg, false, true);log.info("Committed revision: {}", commitInfo.getNewRevision());}private boolean isURLExist(SVNURL url) {try {BasicAuthenticationManager authManager = new BasicAuthenticationManager(getUsername(), getPassword());SVNRepository svnRepository = SVNRepositoryFactory.create(url);svnRepository.setAuthenticationManager(authManager);SVNNodeKind nodeKind = svnRepository.checkPath("", -1);return nodeKind == SVNNodeKind.NONE ? false : true;} catch (SVNException e) {log.error("isURLExist error", e);}return false;}private SVNURL getSvnUrl(File file) throws SVNException {String svnUrl = StrUtil.replace(file.getAbsolutePath(), getRepLocalBasePath(), getRepUrl());svnUrl = svnUrl.replace("\\", "/");log.info("getSvnUrl: {}", svnUrl);return SVNURL.parseURIEncoded(svnUrl);}/**
获取svn指定子目录的最后提交版本。
*/public long getRepVersion() {try {log.info("getRepVersion");BasicAuthenticationManager authManager = new BasicAuthenticationManager(getUsername(), getPassword());SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(getRepUrl()));log.info("getRepVersion repository.getLocation():{}", repository.getLocation().toString());repository.setAuthenticationManager(authManager);long version = repository.getLatestRevision();log.info("getRepVersion version:{}", version);File versionFile = new File(getRepLocalBasePath() + getSvnVersionPath());SVNStatus status = SVNClientManager.newInstance(null, authManager).getStatusClient().doStatus(versionFile, false);if (status != null) {version = status.getCommittedRevision().getNumber();}return version;} catch (Exception e) {log.error("getRepVersion error", e);}return -1;}/**
获取svn指定版本的日志信息.
*/public String getLogInfo(long revision) {try {BasicAuthenticationManager authManager = new BasicAuthenticationManager(getUsername(), getPassword());SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(getRepUrl()));log.info("getRepVersion repository.getLocation():{}", repository.getLocation().toString());repository.setAuthenticationManager(authManager);log.info("getRepVersion version:{}", revision);File versionFile = new File(getRepLocalBasePath() + getSvnVersionPath());StringBuffer logInfoBuf = new StringBuffer();ISVNLogEntryHandler handler = logEntry -> {String logInfo = String.format("%s %s",DateUtil.format(logEntry.getDate(), "yyyyMMddHH:mm:ss"),logEntry.getMessage());logInfoBuf.append(logInfo);log.info("logInfo {}: {}", logEntry.getRevision(), logInfo);};SVNLogClient logClient = new SVNLogClient(authManager, null);logClient.doLog(new File[]{versionFile},SVNRevision.create(revision), SVNRevision.create(revision),true, true,1, handler);return logInfoBuf.toString();} catch (Exception e) {log.error("getLogInfo error", e);}return "";}
# ccs编译命令
@echo off
set ccs_home=E:\programe\ccs124
set workspace=yyyy
set proj_home=xxxxset eclipsec="%ccs_home%\ccs\eclipse\eclipsec"
set proj_name=zzzrem rmdir /S /Q "%proj_home%"\Release
rem TortoiseProc.exe /command:remove /y /path:"%proj_home%\Release\"rmdir /S /Q "%workspace%"mkdir "%workspace%"rem 导入项目"%eclipsec%" -noSplash -data "%workspace%" -application com.ti.ccstudio.apps.projectImport -ccs.location "%proj_home%" -ccs.renameTo "%proj_name%" >> ./logs/gmakeLog_%date:~0,4%%date:~5,2%%date:~8,2%.logrem 清空项目.
"%eclipsec%" -noSplash -data "%workspace%" -application com.ti.ccstudio.apps.projectBuild -ccs.projects "%proj_name%" -ccs.clean >> ./logs/gmakeLog_%date:~0,4%%date:~5,2%%date:~8,2%.logrem 编译.
"%eclipsec%" -noSplash -data "%workspace%" -application com.ti.ccstudio.apps.projectBuild -ccs.projects "%proj_name%" -ccs.configuration Release >> ./logs/gmakeLog_%date:~0,4%%date:~5,2%%date:~8,2%.log