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

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

The newest version!
/*
 * Copyright (c) 2014-2024 Takari, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache Software License v2.0
 * which accompanies this distribution, and is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 */
package io.takari.maven.builder.smart;

import io.takari.maven.builder.smart.ProjectExecutorService.ProjectRunnable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.BuildSummary;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder;
import org.apache.maven.lifecycle.internal.ReactorContext;
import org.apache.maven.lifecycle.internal.TaskSegment;
import org.apache.maven.lifecycle.internal.builder.Builder;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Maven {@link Builder} implementation that schedules execution of the reactor modules on the build
 * critical path first. Build critical path is estimated based on module build times collected
 * during a previous build, or based on module's downstream dependency trail length, if no prior
 * build time information is available.
 *
 * @author Brian Toal
 */
class SmartBuilderImpl {

    private final Logger logger = LoggerFactory.getLogger(SmartBuilder.class);

    // global components
    private final LifecycleModuleBuilder lifecycleModuleBuilder;

    // session-level components
    private final MavenSession rootSession;
    private final ReactorContext reactorContext;
    private final TaskSegment taskSegment;

    //
    private final ReactorBuildQueue reactorBuildQueue;
    private final ProjectExecutorService executor;
    private final int degreeOfConcurrency;

    //
    private final ReactorBuildStats stats;

    private final boolean needsSessionClone;

    class ProjectBuildTask implements ProjectRunnable {
        private final MavenProject project;

        ProjectBuildTask(MavenProject project) {
            this.project = project;
        }

        @Override
        public void run() {
            final long start = System.nanoTime();
            try {
                buildProject(project);
            } finally {
                stats.recordServiceTime(project, System.nanoTime() - start);
            }
        }

        @Override
        public MavenProject getProject() {
            return project;
        }
    }

    SmartBuilderImpl(
            LifecycleModuleBuilder lifecycleModuleBuilder,
            MavenSession session,
            ReactorContext reactorContext,
            TaskSegment taskSegment,
            Set projects,
            DependencyGraph graph) {
        this.lifecycleModuleBuilder = lifecycleModuleBuilder;
        this.rootSession = session;
        this.reactorContext = reactorContext;
        this.taskSegment = taskSegment;

        this.degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency();

        final Comparator projectComparator = ProjectComparator.create(graph);

        this.reactorBuildQueue = new ReactorBuildQueue(projects, graph);
        this.executor = new ProjectExecutorService(degreeOfConcurrency, projectComparator);

        this.stats = ReactorBuildStats.create(projects);

        this.needsSessionClone = needsSessionClone(rootSession.getSystemProperties());
    }

    public ReactorBuildStats build() throws ExecutionException, InterruptedException {
        stats.recordStart();

        Set rootProjects = reactorBuildQueue.getRootProjects();

        // this is the main build loop
        submitAll(rootProjects);
        long timstampSubmit = System.nanoTime();
        int submittedCount = rootProjects.size();
        while (submittedCount > 0) {
            Set bottlenecks = null;
            if (submittedCount < degreeOfConcurrency) {
                bottlenecks = reactorBuildQueue.getReadyProjects();
            }

            try {
                MavenProject completedProject = executor.take();
                if (bottlenecks != null) {
                    stats.recordBottlenecks(bottlenecks, degreeOfConcurrency, System.nanoTime() - timstampSubmit);
                }
                logCompleted(completedProject);
                Set readyProjects = reactorBuildQueue.onProjectFinish(completedProject);
                submitAll(readyProjects);
                timstampSubmit = System.nanoTime();
                submittedCount += (readyProjects.size() - 1);

                logBuildQueueStatus();
            } catch (ExecutionException e) {
                // we get here when unhandled exception or error occurred on the worker thread
                // this can be low-level system problem, like OOME, or runtime exception in maven code
                // there is no meaningful recovery, so we shutdown and rethrow the exception
                shutdown();
                throw e;
            }
        }
        shutdown();

        stats.recordStop();
        return stats;
    }

    private void logBuildQueueStatus() {
        int blockedCount = reactorBuildQueue.getBlockedCount();
        int finishedCount = reactorBuildQueue.getFinishedCount();
        int readyCount = reactorBuildQueue.getReadyCount();
        String runningProjects = "";
        if (readyCount < degreeOfConcurrency && blockedCount > 0) {
            runningProjects = reactorBuildQueue.getReadyProjects().stream()
                    .map(SmartBuilderImpl::projectGA)
                    .collect(Collectors.joining(" ", "[", "]"));
        }
        logger.debug(
                "Builder state: blocked={} finished={} ready-or-running={} {}",
                blockedCount,
                finishedCount,
                readyCount,
                runningProjects);
    }

    private void logCompleted(MavenProject project) {
        BuildSummary buildSummary = rootSession.getResult().getBuildSummary(project);
        String message = "SKIPPED";
        if (buildSummary instanceof BuildSuccess) {
            message = "SUCCESS";
        } else if (buildSummary instanceof BuildFailure) {
            message = "FAILURE";
        } else if (buildSummary != null) {
            logger.debug("Unexpected project build summary class {}", buildSummary.getClass());
            message = "UNKNOWN";
        }
        logger.debug("{} build of project {}:{}", message, project.getGroupId(), project.getArtifactId());
    }

    private static String projectGA(MavenProject project) {
        return project.getGroupId() + ":" + project.getArtifactId();
    }

    private void shutdown() {
        executor.shutdown();
    }

    public void cancel() {
        executor.cancel();
    }

    private void submitAll(Set readyProjects) {
        List tasks = new ArrayList<>();
        for (MavenProject project : readyProjects) {
            tasks.add(new ProjectBuildTask(project));
            logger.debug("Ready {}:{}", project.getGroupId(), project.getArtifactId());
        }
        executor.submitAll(tasks);
    }

    /* package */ void buildProject(MavenProject project) {
        logger.debug("STARTED build of project {}:{}", project.getGroupId(), project.getArtifactId());

        try {
            MavenSession copiedSession = needsSessionClone ? rootSession.clone() : rootSession;
            lifecycleModuleBuilder.buildProject(copiedSession, rootSession, reactorContext, project, taskSegment);
        } catch (RuntimeException ex) {
            // preserve the xml stack trace, and the java cause chain
            rootSession.getResult().addException(new RuntimeException(project.getName() + ": " + ex.getMessage(), ex));
        }
    }

    private static boolean needsSessionClone(Properties props) {
        String version = props.getProperty("maven.version");
        if (version != null) {
            int i0 = version.indexOf(".");
            int i1 = i0 > 0 ? version.indexOf(".", i0 + 1) : -1;
            if (i1 > 0) {
                try {
                    int maj = Integer.parseInt(version.substring(0, i0));
                    int min = Integer.parseInt(version.substring(i0 + 1, i1));
                    if (maj > 3 || (maj == 3 && min >= 9)) {
                        return false;
                    }
                } catch (NumberFormatException e) {
                    // ignore
                }
            }
        }
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy