因工作需要,用户提出希望可以做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 '^编译'; then echo "提交日志以'编译'开头。" >> ${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=xxxx
set eclipsec="%ccs_home%\ccs\eclipse\eclipsec"
set proj_name=zzz
rem 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%.log
rem 清空项目.
"%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%.log
rem 编译.
"%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