如果你也有一个老旧的Java Web应用,因为各种原因,代码库中的代码是不完整的,所以每次上线只能增量部署,或者研发规范里就要求增量部署,在这种情况下如何实现自动化的编译和部署呢?下面给出一个可行的方案:
1、部署前,将需要部署的代码合并到deliver分支
2、比较deliver分支和master分支的差异,得到差异列表之后,用于后续的编译
3、编译差异文件时,需要将应用所依赖的jar,已经部署的class文件等加入到CLASSPATH
4、如果各测试环境也使用这一套自动部署的方案,可能还需要考虑剥离环境相关的配置到配置文件中,并根据环境分目录存放,具体可以参考maven的profile机制
核心代码(代码结构请自行优化 :D):
1、比较两个分支的差异(忽略whitespace带来的差异): try (Git git = Git.open(gitRepoFile); ByteArrayOutputStream out = new ByteArrayOutputStream(); DiffFormatter df = new DiffFormatter(out);) { Repository repository = git.getRepository(); ObjectReader reader = repository.newObjectReader(); String branchName = repository.getBranch(); ObjectId masterId = repository.resolve("remotes/origin/master^{tree}"); ObjectId branchId = repository.resolve(branchName + "^{tree}"); CanonicalTreeParser masterTreeParser = new CanonicalTreeParser(); masterTreeParser.reset(reader, masterId); CanonicalTreeParser branchTreeParser = new CanonicalTreeParser(); branchTreeParser.reset(reader, branchId); List diffs = git.diff() .setNewTree(branchTreeParser) .setOldTree(masterTreeParser) .call(); Map> diffFileMap = new HashMap<>(); List changeFileList = new ArrayList<>(); List deleteFileList = new ArrayList<>(); df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL); df.setRepository(git.getRepository()); for (DiffEntry diffEntry : diffs) { df.format(diffEntry); FileHeader fileHeader = df.toFileHeader(diffEntry); @SuppressWarnings("unchecked") List hunks = (List) fileHeader.getHunks(); int changedSize = 0; for (HunkHeader hunkHeader : hunks) { EditList editList = hunkHeader.toEditList(); for (Edit edit : editList) { changedSize += edit.getEndA() - edit.getBeginA(); changedSize += edit.getEndB() - edit.getBeginB(); } } if (changedSize > 0) { ChangeType changeType = diffEntry.getChangeType(); if (ChangeType.DELETE.equals(changeType)) { String oldFilePath = diffEntry.getOldPath(); log.info("{}|{}", changeType.name(), oldFilePath); deleteFileList.add(oldFilePath); } else { String newFilePath = diffEntry.getNewPath(); log.info("{}|{}", changeType.name(), newFilePath); changeFileList.add(newFilePath); } } } diffFileMap.put("change", changeFileList); diffFileMap.put("delete", deleteFileList); return diffFileMap; }
2、编译: Iterable options = Arrays.asList( "-classpath", classpath, "-encoding", encoding, "-source", jdkVersion, "-target", jdkVersion, "-d", targetPath); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); log.info("classpath:{}", classpath); log.info("targetPath:{}", targetPath); try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);) { Iterable extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(fileList); DiagnosticCollector diagnostics = new DiagnosticCollector<>(); CompilationTask compilationTask = compiler .getTask(null, fileManager, diagnostics, options, null, compilationUnits); if (!compilationTask.call()) { log.error("JavaCompiler Build failed:"); for (Diagnostic> diagnostic : diagnostics.getDiagnostics()) { long line = diagnostic.getLineNumber(); String source = diagnostic.getSource() != null ? diagnostic.getSource().toString() : ""; String kind = diagnostic.getKind().name(); String message = diagnostic.getMessage(null); log.info("{} on line:{} in {}.", kind, line, source); log.info("message:{}", message); } return false; } }
要实现完整的功能,还需要根据应用的具体情况完善,比如:从运行环境同步删除代码库中删除的文件;从代码库拉取文件时,用fetch,reset的方式;还可以借助Marathon、Mesos、Docker运行应用等。