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 the given scopes. 099 * 100 * @param runnableProject the project to resolve dependencies for. 101 * @param scopes the scopes to resolve dependencies for. 102 * @return the list of dependencies. 103 */ 104 public List<Dependency> resolveDependencies(MavenProject runnableProject, String... scopes) { 105 try { 106 DependencyFilter filter = DependencyFilterUtils.andFilter( 107 DependencyFilterUtils.classpathFilter(scopes), 108 new ReactorProjectsFilter(mavenSession.getAllProjects()) 109 ); 110 RepositorySystemSession session = mavenSession.getRepositorySession(); 111 DependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(runnableProject, session); 112 dependencyResolutionRequest.setResolutionFilter(filter); 113 DependencyResolutionResult result = resolver.resolve(dependencyResolutionRequest); 114 return result.getDependencies(); 115 } catch (org.apache.maven.project.DependencyResolutionException e) { 116 if (log.isWarnEnabled()) { 117 log.warn("Error while trying to resolve dependencies for the current project", e); 118 } 119 return Collections.emptyList(); 120 } 121 } 122 123 /** 124 * Builds a classpath string for the given dependencies. 125 * 126 * @param dependencies the dependencies to build the classpath for. 127 * @return the classpath string. 128 */ 129 public String buildClasspath(List<Dependency> dependencies) { 130 Comparator<Dependency> byGroupId = Comparator.comparing(d -> d.getArtifact().getGroupId()); 131 Comparator<Dependency> byArtifactId = Comparator.comparing(d -> d.getArtifact().getArtifactId()); 132 return dependencies.stream() 133 .sorted(byGroupId.thenComparing(byArtifactId)) 134 .map(dependency -> dependency.getArtifact().getFile().getAbsolutePath()) 135 .collect(Collectors.joining(File.pathSeparator)); 136 } 137 138 /** 139 * Packages the project by invoking the Jar plugin. 140 * 141 * @return the invocation result. 142 */ 143 public InvocationResult packageProject() throws MavenInvocationException { 144 return executorService.invokeGoal(MAVEN_JAR_PLUGIN, "jar"); 145 } 146 147 private record ReactorProjectsFilter(List<MavenProject> reactorProjects) implements DependencyFilter { 148 149 @Override 150 public boolean accept(final DependencyNode node, final List<DependencyNode> parents) { 151 final Artifact nodeArtifact = node.getArtifact(); 152 if (nodeArtifact == null) { 153 return true; 154 } 155 for (MavenProject project : reactorProjects) { 156 if (project.getGroupId().equals(nodeArtifact.getGroupId()) && 157 project.getArtifactId().equals(nodeArtifact.getArtifactId()) && 158 project.getVersion().equals(nodeArtifact.getVersion())) { 159 return false; 160 } 161 } 162 return true; 163 } 164 } 165}