001/*
002 * Copyright 2017-2022 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven.services;
017
018import org.apache.maven.execution.MavenSession;
019import org.apache.maven.plugin.logging.Log;
020import org.apache.maven.plugin.logging.SystemStreamLog;
021import org.apache.maven.project.DefaultDependencyResolutionRequest;
022import org.apache.maven.project.DependencyResolutionRequest;
023import org.apache.maven.project.DependencyResolutionResult;
024import org.apache.maven.project.MavenProject;
025import org.apache.maven.project.ProjectDependenciesResolver;
026import org.apache.maven.shared.invoker.InvocationResult;
027import org.apache.maven.shared.invoker.MavenInvocationException;
028import org.eclipse.aether.RepositorySystemSession;
029import org.eclipse.aether.graph.Dependency;
030import org.eclipse.aether.graph.DependencyFilter;
031import org.eclipse.aether.util.filter.DependencyFilterUtils;
032
033import javax.inject.Inject;
034import javax.inject.Singleton;
035import java.io.File;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.List;
039import java.util.Optional;
040import java.util.stream.Collectors;
041
042/**
043 * Provides methods to compile a Maven project.
044 *
045 * @author Álvaro Sánchez-Mariscal
046 * @since 1.1
047 */
048@Singleton
049public class CompilerService {
050
051    public static final String MAVEN_JAR_PLUGIN = "org.apache.maven.plugins:maven-jar-plugin";
052
053    private static final String COMPILE_GOAL = "compile";
054
055    private final Log log;
056    private final MavenSession mavenSession;
057    private final ExecutorService executorService;
058    private final ProjectDependenciesResolver resolver;
059
060    @SuppressWarnings("MnInjectionPoints")
061    @Inject
062    public CompilerService(MavenSession mavenSession, ExecutorService executorService,
063                           ProjectDependenciesResolver resolver) {
064        this.mavenSession = mavenSession;
065        this.resolver = resolver;
066        this.log = new SystemStreamLog();
067        this.executorService = executorService;
068    }
069
070    /**
071     * Compiles the project.
072     *
073     * @return the last compilation time millis.
074     */
075    public Optional<Long> compileProject() {
076        Long lastCompilation = null;
077        if (log.isDebugEnabled()) {
078            log.debug("Compiling the project");
079        }
080        try {
081            executorService.invokeGoals(COMPILE_GOAL);
082            lastCompilation = System.currentTimeMillis();
083        } catch (Exception e) {
084            if (log.isErrorEnabled()) {
085                log.error("Error while compiling the project: ", e);
086            }
087        }
088        return Optional.ofNullable(lastCompilation);
089    }
090
091    /**
092     * Resolves project dependencies for the given scopes.
093     *
094     * @param runnableProject the project to resolve dependencies for.
095     * @param scopes the scopes to resolve dependencies for.
096     * @return the list of dependencies.
097     */
098    public List<Dependency> resolveDependencies(MavenProject runnableProject, String... scopes) {
099        try {
100            DependencyFilter filter = DependencyFilterUtils.classpathFilter(scopes);
101            RepositorySystemSession session = mavenSession.getRepositorySession();
102            DependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(runnableProject, session);
103            dependencyResolutionRequest.setResolutionFilter(filter);
104            DependencyResolutionResult result = resolver.resolve(dependencyResolutionRequest);
105            return result.getDependencies();
106        } catch (org.apache.maven.project.DependencyResolutionException e) {
107            if (log.isWarnEnabled()) {
108                log.warn("Error while trying to resolve dependencies for the current project", e);
109            }
110            return Collections.emptyList();
111        }
112    }
113
114    /**
115     * Builds a classpath string for the given dependencies.
116     *
117     * @param dependencies the dependencies to build the classpath for.
118     * @return the classpath string.
119     */
120    public String buildClasspath(List<Dependency> dependencies) {
121        Comparator<Dependency> byGroupId = Comparator.comparing(d -> d.getArtifact().getGroupId());
122        Comparator<Dependency> byArtifactId = Comparator.comparing(d -> d.getArtifact().getArtifactId());
123        return dependencies.stream()
124            .sorted(byGroupId.thenComparing(byArtifactId))
125            .map(dependency -> dependency.getArtifact().getFile().getAbsolutePath())
126            .collect(Collectors.joining(File.pathSeparator));
127    }
128
129    /**
130     * Packages the project by invoking the Jar plugin.
131     *
132     * @return the invocation result.
133     */
134    public InvocationResult packageProject() throws MavenInvocationException {
135        return executorService.invokeGoal(MAVEN_JAR_PLUGIN, "jar");
136    }
137}