All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.maven.builder.smart.ProjectComparator Maven / Gradle / Ivy

There is a newer version: 1.0.2
Show newest version
package io.takari.maven.builder.smart;

import io.takari.maven.builder.smart.BuildMetrics.Timer;

import java.io.*;
import java.util.*;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.project.MavenProject;

/**
 * Project comparator (factory) that uses project build time to establish build order.
 * 

* Internally, each project is assigned a weight, which is calculated as sum of project build time * and maximum weight of any of the project's downstream dependencies. The project weights are * calculated by recursively traversing project dependency graph starting from build root projects, * i.e. projects that do not have any upstream dependencies. *

* Project build times are estimated based on values persisted during a previous build. Average * build time is used for projects that do not have persisted build time. *

* If there are no persisted build times, all projects build times are assumed the same (arbitrary) * value of 1. This means that the project with the longest downstream dependency trail will be * built first. *

* Currently, historical build times are stored in * ${session.request/baseDirectory}/.mvn/timing.properties file. The timings file is * written only if ${session.request/baseDirectory}/.mvn directory is already present. */ class ProjectComparator { public static Comparator create(MavenSession session) { final ProjectDependencyGraph dependencyGraph = session.getProjectDependencyGraph(); final Map historicalServiceTimes = readServiceTimes(session); return create(dependencyGraph, historicalServiceTimes); } // public for unit testing public static Comparator create(final ProjectDependencyGraph dependencyGraph, final Map historicalServiceTimes) { final long defaultServiceTime = average(historicalServiceTimes.values()); final Map serviceTimes = new HashMap<>(); final Set rootProjects = new HashSet(); for (MavenProject project : dependencyGraph.getSortedProjects()) { Long serviceTime = getServiceTime(historicalServiceTimes, project, defaultServiceTime); serviceTimes.put(project, serviceTime); if (dependencyGraph.getUpstreamProjects(project, false).isEmpty()) { rootProjects.add(project); } } final Map projectWeights = calculateWeights(dependencyGraph, serviceTimes, rootProjects); return new Comparator() { @Override public int compare(MavenProject o1, MavenProject o2) { long delta = projectWeights.get(o2) - projectWeights.get(o1); if (delta > 0) { return 1; } else if (delta < 0) { return -1; } // id comparison guarantees stable ordering during unit tests return id(o2).compareTo(id(o1)); } }; } private static long average(Collection values) { long count = 0, sum = 0; for (Long value : values) { if (value != null) { sum += value.longValue(); count++; } } long average = 0; if (count > 0) { average = sum / count; } if (average == 0) { average = 1; // arbitrary number } return average; } private static long parseServiceTime(String string) { try { long value = Long.parseLong(string); return value > 0 ? value : 0; } catch (NumberFormatException e) { return 0; } } private static Long getServiceTime(Map serviceTimes, MavenProject project, long defaultServiceTime) { Long serviceTime = serviceTimes.get(id(project)); return serviceTime != null ? serviceTime.longValue() : defaultServiceTime; } private static Map calculateWeights(ProjectDependencyGraph dependencyGraph, Map serviceTimes, Collection rootProjects) { Map weights = new HashMap(); for (MavenProject rootProject : rootProjects) { calculateWeights(dependencyGraph, serviceTimes, rootProject, weights); } return weights; } /** * Returns the maximum sum of build time along a path from the project to an exit project. An * "exit project" is a project without downstream dependencies. */ private static long calculateWeights(ProjectDependencyGraph dependencyGraph, Map serviceTimes, MavenProject project, Map weights) { long weight = serviceTimes.get(project); for (MavenProject successor : dependencyGraph.getDownstreamProjects(project, false)) { long successorWeight; if (weights.containsKey(successor)) { successorWeight = weights.get(successor); } else { successorWeight = calculateWeights(dependencyGraph, serviceTimes, successor, weights); } weight = Math.max(weight, serviceTimes.get(project) + successorWeight); } weights.put(project, weight); return weight; } private static Map readServiceTimes(MavenSession session) { Map result = new HashMap<>(); final File timingFile = getTimingFile(session); Properties properties = new Properties(); if (timingFile != null) { try (InputStream is = new FileInputStream(timingFile)) { properties.load(is); } catch (IOException e) { // that's ok } for (String id : properties.stringPropertyNames()) { result.put(id, parseServiceTime(properties.getProperty(id))); } } return result; } private static File getTimingFile(MavenSession session) { File mvndir = new File(session.getRequest().getBaseDirectory(), ".mvn"); return mvndir.isDirectory() ? new File(mvndir, "timing.properties") : null; } public static String id(MavenProject project) { StringBuilder sb = new StringBuilder(); sb.append(project.getGroupId()); sb.append(':'); sb.append(project.getArtifactId()); sb.append(':'); sb.append(project.getVersion()); return sb.toString(); } public static void writeServiceTimes(MavenSession session, ProjectsBuildMetrics metrics) throws IOException { final File timingFile = getTimingFile(session); if (timingFile != null) { Properties properties = new Properties(); for (MavenProject project : metrics.getProjects()) { long serviceTime = metrics.getBuildMetrics(project).getMetricMillis(Timer.SERVICETIME_MS); properties.put(id(project), Long.toString(serviceTime)); } try (OutputStream os = new FileOutputStream(timingFile)) { properties.store(os, null); } } } public static Comparator create(ProjectDependencyGraph projectDependencyGraph, ProjectsBuildMetrics projectsBuildMetrics) { Map serviceTimes = new HashMap<>(); for (Map.Entry entry : projectsBuildMetrics.asMap(Timer.SERVICETIME_MS).entrySet()) { serviceTimes.put(id(entry.getKey()), entry.getValue()); } return create(projectDependencyGraph, serviceTimes); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy