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

io.sundr.maven.GenerateBomMojo Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
/*
 * Copyright 2015 The original authors.
 *
 *    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 io.sundr.maven;

import io.sundr.codegen.generator.CodeGeneratorBuilder;
import io.sundr.maven.filter.Filters;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder;
import org.apache.maven.lifecycle.internal.LifecycleTaskSegmentCalculator;
import org.apache.maven.lifecycle.internal.ProjectIndex;
import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
import org.apache.maven.lifecycle.internal.ReactorContext;
import org.apache.maven.lifecycle.internal.TaskSegment;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
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.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Mojo(name = "generate-bom", inheritByDefault = false, defaultPhase = LifecyclePhase.VALIDATE)
public class GenerateBomMojo extends AbstractSundrioMojo {


    private static final Set GENERATED_ARTIFACT_IDS = Collections.synchronizedSet(new HashSet());

    @Component
    private ArtifactResolver artifactResolver;

    @Component
    private LifecycleModuleBuilder builder;

    @Component
    private LifecycleTaskSegmentCalculator segmentCalculator;

    /**
     * Location of the localRepository repository.
     */
    @Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
    private ArtifactRepository localRepository;

    /**
     * List of Remote Repositories used by the resolver
     */
    @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
    protected List remoteRepositories;

    @Parameter
    private BomConfig[] boms;

    @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = false)
    private List reactorProjects;

    @Parameter(defaultValue = "${maven.version}")
    private String mavenVersion;

    @Parameter(defaultValue = "${bom.template.resource}")
    private String bomTemplateResource = "templates/bom.xml.vm";

    @Parameter(defaultValue = "${bom.template.url}")
    private URL bomTemplateUrl;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (getProject().isExecutionRoot() && !getProject().getModules().isEmpty()) {
            List updated = new LinkedList();

            Map generated = new HashMap();
            updated.add(getProject());
            if (boms == null || boms.length == 0) {
                String artifactId = getProject().getArtifactId() + "-bom";
                if (GENERATED_ARTIFACT_IDS.add(artifactId)) {
                    BomConfig cfg = new BomConfig(artifactId, getProject().getName() + " Bom", " Generated bom");
                    MavenProject bomProject = generateBom(cfg);
                    generated.put(cfg, bomProject);
                    updated.add(bomProject);
                }
            } else {
                for (BomConfig cfg : boms) {
                    if (GENERATED_ARTIFACT_IDS.add(cfg.getArtifactId())) {
                        MavenProject bomProject = generateBom(cfg);
                        generated.put(cfg, bomProject);
                        updated.add(bomProject);
                    }
                }
            }

            updated.addAll(getAllButCurrent());
            for (Map.Entry entry : generated.entrySet()) {
                build(getSession().clone(), entry.getValue(), updated, entry.getKey().getGoals());
            }
        }
    }

    private MavenProject generateBom(BomConfig config) throws MojoFailureException, MojoExecutionException {
        File outputDir = new File(getProject().getBuild().getOutputDirectory());
        File bomDir = new File(outputDir, config.getArtifactId());
        File generatedBom = new File(bomDir, BOM_NAME);


        if (!bomDir.exists() && !bomDir.mkdirs()) {
            throw new MojoFailureException("Failed to create output dir for bom:" + bomDir.getAbsolutePath());
        }
        preProccessConfig(config);
        FileWriter writer = null;
        try {
            writer = new FileWriter(generatedBom);
            Set dependencies = new LinkedHashSet();
            Set plugins = new LinkedHashSet();

            Set ownModules = Filters.filter(getReactorArtifacts(), Filters.createModulesFilter(config));

            //We add first project management and unwrapped boms. (we don't resolve those).
            Set dependencyManagementArtifacts =  config.isInheritDependencyManagement() ? getProjectDependencyManagement() : new LinkedHashSet();
            Set pluginManagementArtifacts = config.isInheritPluginManagement() ? getProjectPluginManagement() : new LinkedHashSet();

            Set allDependencies = new LinkedHashSet(dependencyManagementArtifacts);
            allDependencies.addAll(getDependencies(getProjectDependencies())); //We resolve transitives here....

            //Populate dependencies
            dependencies.addAll(ownModules);
            dependencies.addAll(Filters.filter(allDependencies, Filters.createDependencyFilter(getSession(), config)));

            //Populate plugins
            plugins.addAll(Filters.filter(ownModules, Filters.MAVEN_PLUGIN_FILTER));
            plugins.addAll(Filters.filter(pluginManagementArtifacts, Filters.createPluginFilter(getSession(), config)));

            getLog().info("Generating BOM: " + config.getArtifactId());
            new CodeGeneratorBuilder()
                    .withWriter(writer)
                    .withModel(toGenerate(getProject(), config, dependencies, plugins).getModel())
                    .withTemplateResource(bomTemplateResource)
                    .withTemplateUrl(bomTemplateUrl)
                    .build().generate();

            return toBuild(getProject(), config);

        } catch (Exception e) {
            throw new MojoFailureException("Failed to generate bom.", e);
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to close the generated bom writer", e);
            }
        }
    }

    private Set getReactorArtifacts() {
        Set reactorArtifacts = new LinkedHashSet();
        for (MavenProject project : reactorProjects) {
            reactorArtifacts.add(project.getArtifact());
        }
        return reactorArtifacts;
    }

    /**
     * Returns the model of the {@link org.apache.maven.project.MavenProject} to generate.
     * This is a trimmed down version and contains just the stuff that need to go into the bom.
     *
     * @param project The source {@link org.apache.maven.project.MavenProject}.
     * @param config  The {@link io.sundr.maven.BomConfig}.
     * @return The build {@link org.apache.maven.project.MavenProject}.
     */
    private static MavenProject toGenerate(MavenProject project, BomConfig config, Set dependencies, Set plugins) {
        MavenProject toGenerate = project.clone();
        toGenerate.setGroupId(project.getGroupId());
        toGenerate.setArtifactId(config.getArtifactId());
        toGenerate.setVersion(project.getVersion());
        toGenerate.setPackaging("pom");
        toGenerate.setName(config.getName());
        toGenerate.setDescription(config.getDescription());

        toGenerate.setUrl(project.getUrl());
        toGenerate.setLicenses(project.getLicenses());
        toGenerate.setScm(project.getScm());
        toGenerate.setDevelopers(project.getDevelopers());

        toGenerate.getModel().setDependencyManagement(new DependencyManagement());
        for (Artifact artifact : dependencies) {
            toGenerate.getDependencyManagement().addDependency(toDependency(artifact));
        }

        toGenerate.getModel().setBuild(new Build());
        if (!plugins.isEmpty()) {
            toGenerate.getModel().setBuild(new Build());
            toGenerate.getModel().getBuild().setPluginManagement(new PluginManagement());
            for (Artifact artifact : plugins) {
                toGenerate.getPluginManagement().addPlugin(toPlugin(artifact));
            }
        }

        return toGenerate;
    }

    /**
     * Returns the generated {@link org.apache.maven.project.MavenProject} to build.
     * This version of the project contains all the stuff needed for building (parents, profiles, properties etc).
     *
     * @param project The source {@link org.apache.maven.project.MavenProject}.
     * @param config  The {@link io.sundr.maven.BomConfig}.
     * @return The build {@link org.apache.maven.project.MavenProject}.
     */
    private static MavenProject toBuild(MavenProject project, BomConfig config) {
        File outputDir = new File(project.getBuild().getOutputDirectory());
        File bomDir = new File(outputDir, config.getArtifactId());
        File generatedBom = new File(bomDir, BOM_NAME);

        MavenProject toBuild = project.clone();
        //we want to avoid recursive "generate-bom".
        toBuild.setExecutionRoot(false);
        toBuild.setFile(generatedBom);
        toBuild.getModel().setPomFile(generatedBom);
        toBuild.setModelVersion(project.getModelVersion());

        toBuild.setArtifact(new DefaultArtifact(project.getGroupId(),
                config.getArtifactId(), project.getVersion(), project.getArtifact().getScope(),
                project.getArtifact().getType(), project.getArtifact().getClassifier(),
                project.getArtifact().getArtifactHandler()));


        toBuild.setParent(project.getParent());
        toBuild.getModel().setParent(project.getModel().getParent());

        toBuild.setGroupId(project.getGroupId());
        toBuild.setArtifactId(config.getArtifactId());
        toBuild.setVersion(project.getVersion());
        toBuild.setPackaging("pom");
        toBuild.setName(config.getName());
        toBuild.setDescription(config.getDescription());

        toBuild.setUrl(project.getUrl());
        toBuild.setLicenses(project.getLicenses());
        toBuild.setScm(project.getScm());
        toBuild.setDevelopers(project.getDevelopers());
        toBuild.setDistributionManagement(project.getDistributionManagement());
        toBuild.getModel().setProfiles(project.getModel().getProfiles());

        //We want to avoid having the generated stuff wiped.
        toBuild.getProperties().put("clean.skip", "true");
        toBuild.getModel().getBuild().setDirectory(bomDir.getAbsolutePath());
        toBuild.getModel().getBuild().setOutputDirectory(new File(bomDir, "target").getAbsolutePath());
        for (String key : config.getProperties().stringPropertyNames()) {
            toBuild.getProperties().put(key, config.getProperties().getProperty(key));
        }
        return toBuild;
    }

    private List getAllButCurrent() {
        List result = new LinkedList(getSession().getProjects());
        result.remove(getSession().getCurrentProject());
        return result;
    }

    private void build(MavenSession session, MavenProject project, List allProjects, GoalSet goals) throws MojoExecutionException {
        session.setProjects(allProjects);
        ProjectIndex projectIndex = new ProjectIndex(session.getProjects());
        try {
            ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus(new BomDependencyGraph(session.getProjects()));
            ReactorContext reactorContext = new ReactorContextFactory(new MavenVersion(mavenVersion)).create(session.getResult(), projectIndex, Thread.currentThread().getContextClassLoader(), reactorBuildStatus, builder);
            List segments = segmentCalculator.calculateTaskSegments(session);
            for (TaskSegment segment : segments) {
                builder.buildProject(session, reactorContext, project, filterSegment(segment, goals));
            }
        } catch (Throwable t) {
            throw new MojoExecutionException("Error building generated bom:" + project.getArtifactId(), t);
        }
    }

    /**
     * Returns all the session/reactor artifacts topologically sorted.
     *
     * @return
     */
    private Set getSessionArtifacts() {
        Set result = new LinkedHashSet();
        for (MavenProject p : getSession().getProjectDependencyGraph().getSortedProjects()) {
            result.add(p.getArtifact());
        }
        return result;
    }

    /**
     * Returns all dependency artifacts in all modules, excluding all reactor artifacts (including attached).
     *
     * @return
     */
    private Set getProjectDependencies() {
        Set result = new LinkedHashSet();
        for (MavenProject p : getSession().getProjectDependencyGraph().getSortedProjects()) {
            for (Dependency dependency : p.getDependencies()) {
                result.add(toArtifact(dependency));
            }
        }
        return result;
    }

    /**
     * Returns all dependencies defined in dependency management of the root pom.
     *
     * @return
     */
    private Set getProjectDependencyManagement() {
        Set result = new LinkedHashSet();
        DependencyManagement dependencyManagement =  getProject().getDependencyManagement();
        if (dependencyManagement != null) {
            for (Dependency dependency : dependencyManagement.getDependencies()) {
                result.add(toArtifact(dependency));
            }
        }
        return result;
    }

    /**
     * Returns all dependencies defined in dependency management of the root pom.
     *
     * @return
     */
    private Set getProjectPluginManagement() {
        Set result = new LinkedHashSet();
        PluginManagement pluginManagement =  getProject().getPluginManagement();
        if (pluginManagement != null) {
            for (Plugin plugin : pluginManagement.getPlugins()) {
                result.add(toArtifact(plugin));
            }
        }
        return result;
    }

    /**
     * Resolves dependencies.
     *
     * @param dependencies
     * @return
     */
    private Set getDependencies(final Set dependencies) {
        ArtifactResolutionRequest request = new ArtifactResolutionRequest();
        request.setArtifact(getProject().getArtifact());
        request.setArtifactDependencies(dependencies);
        request.setLocalRepository(localRepository);
        request.setRemoteRepositories(remoteRepositories);
        request.setManagedVersionMap(getProject().getManagedVersionMap());
        request.setResolveTransitively(true);
        ArtifactResolutionResult result = artifactResolver.resolve(request);
        return result.getArtifacts();
    }

    private Artifact toArtifact(Dependency dependency) {
        return new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), dependency.getScope(), dependency.getType(), dependency.getClassifier(), getArtifactHandler());
    }

    private Artifact toArtifact(Plugin plugin) {
        return new DefaultArtifact(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion(), null, Constants.MAVEN_PLUGIN_TYPE, null, getArtifactHandler());
    }

    private static Dependency toDependency(Artifact artifact) {
        Dependency dependency = new Dependency();
        dependency.setGroupId(artifact.getGroupId());
        dependency.setArtifactId(artifact.getArtifactId());
        dependency.setVersion(artifact.getVersion());
        dependency.setType(artifact.getType());
        dependency.setScope(artifact.getScope());
        dependency.setClassifier(artifact.getClassifier());
        dependency.setOptional(artifact.isOptional());
        return dependency;
    }

    private static Plugin toPlugin(Artifact artifact) {
        Plugin plugin = new Plugin();
        plugin.setGroupId(artifact.getGroupId());
        plugin.setArtifactId(artifact.getArtifactId());
        plugin.setVersion(artifact.getVersion());
        return plugin;
    }

    private static TaskSegment filterSegment(TaskSegment segment, GoalSet goals) {
        List filtered = new ArrayList();

        Set includes = goals.getIncludes();
        Set excludes = goals.getExcludes();

        for (Object obj : segment.getTasks()) {
            String name = Reflections.readAnyField(obj, "pluginGoal", "lifecyclePhase");

            if (!excludes.contains(name) && (includes.contains(name) || includes.isEmpty())) {
                filtered.add(obj);
            }
        }
        return new TaskSegment(segment.isAggregating(), filtered.toArray());
    }


    private static void preProccessConfig(BomConfig config) {
        if (config.getModules().getIncludes().isEmpty()) {
            config.getModules().getIncludes().add("*:*");
        }
    }
}