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 io.micronaut.maven.InvocationResultWithOutput; 019import org.apache.maven.execution.MavenSession; 020import org.apache.maven.plugin.logging.Log; 021import org.apache.maven.plugin.logging.SystemStreamLog; 022import org.apache.maven.project.DefaultDependencyResolutionRequest; 023import org.apache.maven.project.DependencyResolutionRequest; 024import org.apache.maven.project.DependencyResolutionResult; 025import org.apache.maven.project.MavenProject; 026import org.apache.maven.project.ProjectDependenciesResolver; 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 InvocationResultWithOutput compilationResult = executorService.invokeGoals(projectToCompile, COMPILE_GOAL); 088 if (compilationResult.getExitCode() != 0) { 089 for (String line : compilationResult.outputHandler().getOutput()) { 090 log.error(line); 091 } 092 } 093 lastCompilation = System.currentTimeMillis(); 094 } catch (Exception e) { 095 if (log.isErrorEnabled()) { 096 log.error("Error while compiling the project: ", e); 097 } 098 } 099 return Optional.ofNullable(lastCompilation); 100 } 101 102 /** 103 * Resolves project dependencies for given scopes. 104 * 105 * @param runnableProject The project 106 * @param scopes The scopes 107 * @return The dependencies 108 */ 109 public List<Dependency> resolveDependencies(MavenProject runnableProject, String... scopes) { 110 return resolveDependencies(runnableProject, false, scopes); 111 } 112 113 /** 114 * Resolves project dependencies for the given scopes. 115 * 116 * @param runnableProject the project to resolve dependencies for. 117 * @param excludeProjects Whether to exclude projects (of this build) from the dependencies. 118 * @param scopes the scopes to resolve dependencies for. 119 * @return the list of dependencies. 120 */ 121 public List<Dependency> resolveDependencies(MavenProject runnableProject, boolean excludeProjects, String... scopes) { 122 try { 123 DependencyFilter filter = DependencyFilterUtils.classpathFilter(scopes); 124 if (excludeProjects) { 125 DependencyFilterUtils.andFilter(filter, new ReactorProjectsFilter(mavenSession.getAllProjects())); 126 } 127 RepositorySystemSession session = mavenSession.getRepositorySession(); 128 DependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(runnableProject, session); 129 dependencyResolutionRequest.setResolutionFilter(filter); 130 DependencyResolutionResult result = resolver.resolve(dependencyResolutionRequest); 131 return result.getDependencies(); 132 } catch (org.apache.maven.project.DependencyResolutionException e) { 133 if (log.isWarnEnabled()) { 134 log.warn("Error while trying to resolve dependencies for the current project", e); 135 } 136 return Collections.emptyList(); 137 } 138 } 139 140 /** 141 * Builds a classpath string for the given dependencies. 142 * 143 * @param dependencies the dependencies to build the classpath for. 144 * @return the classpath string. 145 */ 146 public String buildClasspath(List<Dependency> dependencies) { 147 Comparator<Dependency> byGroupId = Comparator.comparing(d -> d.getArtifact().getGroupId()); 148 Comparator<Dependency> byArtifactId = Comparator.comparing(d -> d.getArtifact().getArtifactId()); 149 return dependencies.stream() 150 .sorted(byGroupId.thenComparing(byArtifactId)) 151 .map(dependency -> dependency.getArtifact().getFile().getAbsolutePath()) 152 .collect(Collectors.joining(File.pathSeparator)); 153 } 154 155 /** 156 * Packages the project by invoking the Jar plugin. 157 * 158 * @return the invocation result. 159 */ 160 public InvocationResultWithOutput packageProject() throws MavenInvocationException { 161 return executorService.invokeGoal(MAVEN_JAR_PLUGIN, "jar"); 162 } 163 164 private record ReactorProjectsFilter(List<MavenProject> reactorProjects) implements DependencyFilter { 165 166 @Override 167 public boolean accept(final DependencyNode node, final List<DependencyNode> parents) { 168 final Artifact nodeArtifact = node.getArtifact(); 169 if (nodeArtifact == null) { 170 return true; 171 } 172 for (MavenProject project : reactorProjects) { 173 if (project.getGroupId().equals(nodeArtifact.getGroupId()) && 174 project.getArtifactId().equals(nodeArtifact.getArtifactId()) && 175 project.getVersion().equals(nodeArtifact.getVersion())) { 176 return false; 177 } 178 } 179 return true; 180 } 181 } 182}