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.artifact.Artifact; 030import org.eclipse.aether.graph.Dependency; 031import org.eclipse.aether.graph.DependencyFilter; 032import org.eclipse.aether.graph.DependencyNode; 033import org.eclipse.aether.util.filter.DependencyFilterUtils; 034 035import javax.inject.Inject; 036import javax.inject.Singleton; 037import java.io.File; 038import java.util.Collections; 039import java.util.Comparator; 040import java.util.List; 041import java.util.Optional; 042import java.util.stream.Collectors; 043 044/** 045 * Provides methods to compile a Maven project. 046 * 047 * @author Álvaro Sánchez-Mariscal 048 * @since 1.1 049 */ 050@Singleton 051public class CompilerService { 052 053 public static final String MAVEN_JAR_PLUGIN = "org.apache.maven.plugins:maven-jar-plugin"; 054 055 private static final String COMPILE_GOAL = "compile"; 056 057 private final Log log; 058 private final MavenSession mavenSession; 059 private final ExecutorService executorService; 060 private final ProjectDependenciesResolver resolver; 061 062 @SuppressWarnings("MnInjectionPoints") 063 @Inject 064 public CompilerService(MavenSession mavenSession, ExecutorService executorService, 065 ProjectDependenciesResolver resolver) { 066 this.mavenSession = mavenSession; 067 this.resolver = resolver; 068 this.log = new SystemStreamLog(); 069 this.executorService = executorService; 070 } 071 072 /** 073 * Compiles the project. 074 * 075 * @return the last compilation time millis. 076 */ 077 public Optional<Long> compileProject() { 078 Long lastCompilation = null; 079 if (log.isDebugEnabled()) { 080 log.debug("Compiling the project"); 081 } 082 try { 083 MavenProject projectToCompile = mavenSession.getTopLevelProject(); 084 if (mavenSession.getAllProjects().contains(mavenSession.getCurrentProject().getParent())) { 085 projectToCompile = mavenSession.getCurrentProject().getParent(); 086 } 087 executorService.invokeGoals(projectToCompile, COMPILE_GOAL); 088 lastCompilation = System.currentTimeMillis(); 089 } catch (Exception e) { 090 if (log.isErrorEnabled()) { 091 log.error("Error while compiling the project: ", e); 092 } 093 } 094 return Optional.ofNullable(lastCompilation); 095 } 096 097 /** 098 * Resolves project dependencies for given scopes. 099 * 100 * @param runnableProject The project 101 * @param scopes The scopes 102 * @return The dependencies 103 */ 104 public List<Dependency> resolveDependencies(MavenProject runnableProject, String... scopes) { 105 return resolveDependencies(runnableProject, false, scopes); 106 } 107 108 /** 109 * Resolves project dependencies for the given scopes. 110 * 111 * @param runnableProject the project to resolve dependencies for. 112 * @param excludeProjects Whether to exclude projects (of this build) from the dependencies. 113 * @param scopes the scopes to resolve dependencies for. 114 * @return the list of dependencies. 115 */ 116 public List<Dependency> resolveDependencies(MavenProject runnableProject, boolean excludeProjects, String... scopes) { 117 try { 118 DependencyFilter filter = DependencyFilterUtils.classpathFilter(scopes); 119 if (excludeProjects) { 120 DependencyFilterUtils.andFilter(filter, new ReactorProjectsFilter(mavenSession.getAllProjects())); 121 } 122 RepositorySystemSession session = mavenSession.getRepositorySession(); 123 DependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(runnableProject, session); 124 dependencyResolutionRequest.setResolutionFilter(filter); 125 DependencyResolutionResult result = resolver.resolve(dependencyResolutionRequest); 126 return result.getDependencies(); 127 } catch (org.apache.maven.project.DependencyResolutionException e) { 128 if (log.isWarnEnabled()) { 129 log.warn("Error while trying to resolve dependencies for the current project", e); 130 } 131 return Collections.emptyList(); 132 } 133 } 134 135 /** 136 * Builds a classpath string for the given dependencies. 137 * 138 * @param dependencies the dependencies to build the classpath for. 139 * @return the classpath string. 140 */ 141 public String buildClasspath(List<Dependency> dependencies) { 142 Comparator<Dependency> byGroupId = Comparator.comparing(d -> d.getArtifact().getGroupId()); 143 Comparator<Dependency> byArtifactId = Comparator.comparing(d -> d.getArtifact().getArtifactId()); 144 return dependencies.stream() 145 .sorted(byGroupId.thenComparing(byArtifactId)) 146 .map(dependency -> dependency.getArtifact().getFile().getAbsolutePath()) 147 .collect(Collectors.joining(File.pathSeparator)); 148 } 149 150 /** 151 * Packages the project by invoking the Jar plugin. 152 * 153 * @return the invocation result. 154 */ 155 public InvocationResult packageProject() throws MavenInvocationException { 156 return executorService.invokeGoal(MAVEN_JAR_PLUGIN, "jar"); 157 } 158 159 private record ReactorProjectsFilter(List<MavenProject> reactorProjects) implements DependencyFilter { 160 161 @Override 162 public boolean accept(final DependencyNode node, final List<DependencyNode> parents) { 163 final Artifact nodeArtifact = node.getArtifact(); 164 if (nodeArtifact == null) { 165 return true; 166 } 167 for (MavenProject project : reactorProjects) { 168 if (project.getGroupId().equals(nodeArtifact.getGroupId()) && 169 project.getArtifactId().equals(nodeArtifact.getArtifactId()) && 170 project.getVersion().equals(nodeArtifact.getVersion())) { 171 return false; 172 } 173 } 174 return true; 175 } 176 } 177}