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