|
@@ -0,0 +1,343 @@
|
|
|
+/*
|
|
|
+ * Copyright 2012 The Apache Software Foundation.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package org.apache.hadoop.maven.plugin.versioninfo;
|
|
|
+
|
|
|
+import org.apache.hadoop.maven.plugin.util.Exec;
|
|
|
+import org.apache.hadoop.maven.plugin.util.FileSetUtils;
|
|
|
+import org.apache.maven.model.FileSet;
|
|
|
+import org.apache.maven.plugin.AbstractMojo;
|
|
|
+import org.apache.maven.plugin.MojoExecutionException;
|
|
|
+import org.apache.maven.plugins.annotations.LifecyclePhase;
|
|
|
+import org.apache.maven.plugins.annotations.Mojo;
|
|
|
+import org.apache.maven.plugins.annotations.Parameter;
|
|
|
+import org.apache.maven.project.MavenProject;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.RandomAccessFile;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.text.DateFormat;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.TimeZone;
|
|
|
+
|
|
|
+/**
|
|
|
+ * VersionInfoMojo calculates information about the current version of the
|
|
|
+ * codebase and exports the information as properties for further use in a Maven
|
|
|
+ * build. The version information includes build time, SCM URI, SCM branch, SCM
|
|
|
+ * commit, and an MD5 checksum of the contents of the files in the codebase.
|
|
|
+ */
|
|
|
+@Mojo(name="version-info", defaultPhase=LifecyclePhase.INITIALIZE)
|
|
|
+public class VersionInfoMojo extends AbstractMojo {
|
|
|
+
|
|
|
+ @Parameter(defaultValue="${project}")
|
|
|
+ private MavenProject project;
|
|
|
+
|
|
|
+ @Parameter(required=true)
|
|
|
+ private FileSet source;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="version-info.build.time")
|
|
|
+ private String buildTimeProperty;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="version-info.source.md5")
|
|
|
+ private String md5Property;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="version-info.scm.uri")
|
|
|
+ private String scmUriProperty;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="version-info.scm.branch")
|
|
|
+ private String scmBranchProperty;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="version-info.scm.commit")
|
|
|
+ private String scmCommitProperty;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="git")
|
|
|
+ private String gitCommand;
|
|
|
+
|
|
|
+ @Parameter(defaultValue="svn")
|
|
|
+ private String svnCommand;
|
|
|
+
|
|
|
+ private enum SCM {NONE, SVN, GIT}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void execute() throws MojoExecutionException {
|
|
|
+ try {
|
|
|
+ SCM scm = determineSCM();
|
|
|
+ project.getProperties().setProperty(buildTimeProperty, getBuildTime());
|
|
|
+ project.getProperties().setProperty(scmUriProperty, getSCMUri(scm));
|
|
|
+ project.getProperties().setProperty(scmBranchProperty, getSCMBranch(scm));
|
|
|
+ project.getProperties().setProperty(scmCommitProperty, getSCMCommit(scm));
|
|
|
+ project.getProperties().setProperty(md5Property, computeMD5());
|
|
|
+ } catch (Throwable ex) {
|
|
|
+ throw new MojoExecutionException(ex.toString(), ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns a string representing current build time.
|
|
|
+ *
|
|
|
+ * @return String representing current build time
|
|
|
+ */
|
|
|
+ private String getBuildTime() {
|
|
|
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
|
|
|
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
|
+ return dateFormat.format(new Date());
|
|
|
+ }
|
|
|
+ private List<String> scmOut;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determines which SCM is in use (Subversion, git, or none) and captures
|
|
|
+ * output of the SCM command for later parsing.
|
|
|
+ *
|
|
|
+ * @return SCM in use for this build
|
|
|
+ * @throws Exception if any error occurs attempting to determine SCM
|
|
|
+ */
|
|
|
+ private SCM determineSCM() throws Exception {
|
|
|
+ Exec exec = new Exec(this);
|
|
|
+ SCM scm = SCM.NONE;
|
|
|
+ scmOut = new ArrayList<String>();
|
|
|
+ int ret = exec.run(Arrays.asList(svnCommand, "info"), scmOut);
|
|
|
+ if (ret == 0) {
|
|
|
+ scm = SCM.SVN;
|
|
|
+ } else {
|
|
|
+ ret = exec.run(Arrays.asList(gitCommand, "branch"), scmOut);
|
|
|
+ if (ret == 0) {
|
|
|
+ ret = exec.run(Arrays.asList(gitCommand, "remote", "-v"), scmOut);
|
|
|
+ if (ret != 0) {
|
|
|
+ scm = SCM.NONE;
|
|
|
+ scmOut = null;
|
|
|
+ } else {
|
|
|
+ ret = exec.run(Arrays.asList(gitCommand, "log", "-n", "1"), scmOut);
|
|
|
+ if (ret != 0) {
|
|
|
+ scm = SCM.NONE;
|
|
|
+ scmOut = null;
|
|
|
+ } else {
|
|
|
+ scm = SCM.GIT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (scmOut != null) {
|
|
|
+ getLog().debug(scmOut.toString());
|
|
|
+ }
|
|
|
+ getLog().info("SCM: " + scm);
|
|
|
+ return scm;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return URI and branch of Subversion repository.
|
|
|
+ *
|
|
|
+ * @param str String Subversion info output containing URI and branch
|
|
|
+ * @return String[] containing URI and branch
|
|
|
+ */
|
|
|
+ private String[] getSvnUriInfo(String str) {
|
|
|
+ String[] res = new String[]{"Unknown", "Unknown"};
|
|
|
+ try {
|
|
|
+ String path = str;
|
|
|
+ int index = path.indexOf("trunk");
|
|
|
+ if (index > -1) {
|
|
|
+ res[0] = path.substring(0, index - 1);
|
|
|
+ res[1] = "trunk";
|
|
|
+ } else {
|
|
|
+ index = path.indexOf("branches");
|
|
|
+ if (index > -1) {
|
|
|
+ res[0] = path.substring(0, index - 1);
|
|
|
+ int branchIndex = index + "branches".length() + 1;
|
|
|
+ index = path.indexOf("/", branchIndex);
|
|
|
+ if (index > -1) {
|
|
|
+ res[1] = path.substring(branchIndex, index);
|
|
|
+ } else {
|
|
|
+ res[1] = path.substring(branchIndex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ex) {
|
|
|
+ getLog().warn("Could not determine URI & branch from SVN URI: " + str);
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parses SCM output and returns URI of SCM.
|
|
|
+ *
|
|
|
+ * @param scm SCM in use for this build
|
|
|
+ * @return String URI of SCM
|
|
|
+ */
|
|
|
+ private String getSCMUri(SCM scm) {
|
|
|
+ String uri = "Unknown";
|
|
|
+ switch (scm) {
|
|
|
+ case SVN:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("URL:")) {
|
|
|
+ uri = s.substring(4).trim();
|
|
|
+ uri = getSvnUriInfo(uri)[0];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case GIT:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("origin") && s.endsWith("(fetch)")) {
|
|
|
+ uri = s.substring("origin".length());
|
|
|
+ uri = uri.substring(0, uri.length() - "(fetch)".length());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return uri.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parses SCM output and returns commit of SCM.
|
|
|
+ *
|
|
|
+ * @param scm SCM in use for this build
|
|
|
+ * @return String commit of SCM
|
|
|
+ */
|
|
|
+ private String getSCMCommit(SCM scm) {
|
|
|
+ String commit = "Unknown";
|
|
|
+ switch (scm) {
|
|
|
+ case SVN:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("Revision:")) {
|
|
|
+ commit = s.substring("Revision:".length());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case GIT:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("commit")) {
|
|
|
+ commit = s.substring("commit".length());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return commit.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parses SCM output and returns branch of SCM.
|
|
|
+ *
|
|
|
+ * @param scm SCM in use for this build
|
|
|
+ * @return String branch of SCM
|
|
|
+ */
|
|
|
+ private String getSCMBranch(SCM scm) {
|
|
|
+ String branch = "Unknown";
|
|
|
+ switch (scm) {
|
|
|
+ case SVN:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("URL:")) {
|
|
|
+ branch = s.substring(4).trim();
|
|
|
+ branch = getSvnUriInfo(branch)[1];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case GIT:
|
|
|
+ for (String s : scmOut) {
|
|
|
+ if (s.startsWith("*")) {
|
|
|
+ branch = s.substring("*".length());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return branch.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Reads and returns the full contents of the specified file.
|
|
|
+ *
|
|
|
+ * @param file File to read
|
|
|
+ * @return byte[] containing full contents of file
|
|
|
+ * @throws IOException if there is an I/O error while reading the file
|
|
|
+ */
|
|
|
+ private byte[] readFile(File file) throws IOException {
|
|
|
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
|
|
|
+ byte[] buffer = new byte[(int) raf.length()];
|
|
|
+ raf.readFully(buffer);
|
|
|
+ raf.close();
|
|
|
+ return buffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Given a list of files, computes and returns an MD5 checksum of the full
|
|
|
+ * contents of all files.
|
|
|
+ *
|
|
|
+ * @param files List<File> containing every file to input into the MD5 checksum
|
|
|
+ * @return byte[] calculated MD5 checksum
|
|
|
+ * @throws IOException if there is an I/O error while reading a file
|
|
|
+ * @throws NoSuchAlgorithmException if the MD5 algorithm is not supported
|
|
|
+ */
|
|
|
+ private byte[] computeMD5(List<File> files) throws IOException, NoSuchAlgorithmException {
|
|
|
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
|
+ for (File file : files) {
|
|
|
+ getLog().debug("Computing MD5 for: " + file);
|
|
|
+ md5.update(readFile(file));
|
|
|
+ }
|
|
|
+ return md5.digest();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Converts bytes to a hexadecimal string representation and returns it.
|
|
|
+ *
|
|
|
+ * @param array byte[] to convert
|
|
|
+ * @return String containing hexadecimal representation of bytes
|
|
|
+ */
|
|
|
+ private String byteArrayToString(byte[] array) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (byte b : array) {
|
|
|
+ sb.append(Integer.toHexString(0xff & b));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Computes and returns an MD5 checksum of the contents of all files in the
|
|
|
+ * input Maven FileSet.
|
|
|
+ *
|
|
|
+ * @return String containing hexadecimal representation of MD5 checksum
|
|
|
+ * @throws Exception if there is any error while computing the MD5 checksum
|
|
|
+ */
|
|
|
+ private String computeMD5() throws Exception {
|
|
|
+ List<File> files = FileSetUtils.convertFileSetToFiles(source);
|
|
|
+ // File order of MD5 calculation is significant. Sorting is done on
|
|
|
+ // unix-format names, case-folded, in order to get a platform-independent
|
|
|
+ // sort and calculate the same MD5 on all platforms.
|
|
|
+ Collections.sort(files, new Comparator<File>() {
|
|
|
+ @Override
|
|
|
+ public int compare(File lhs, File rhs) {
|
|
|
+ return normalizePath(lhs).compareTo(normalizePath(rhs));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String normalizePath(File file) {
|
|
|
+ return file.getPath().toUpperCase().replaceAll("\\\\", "/");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ byte[] md5 = computeMD5(files);
|
|
|
+ String md5str = byteArrayToString(md5);
|
|
|
+ getLog().info("Computed MD5: " + md5str);
|
|
|
+ return md5str;
|
|
|
+ }
|
|
|
+}
|