View Javadoc
1   /*
2    * Copyright 2017-2022 original authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package io.micronaut.build.services;
17  
18  import org.apache.maven.execution.MavenSession;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.apache.maven.plugin.logging.Log;
21  import org.apache.maven.plugin.logging.SystemStreamLog;
22  import org.apache.maven.project.*;
23  import org.apache.maven.shared.invoker.*;
24  import org.eclipse.aether.RepositorySystemSession;
25  import org.eclipse.aether.graph.Dependency;
26  import org.eclipse.aether.graph.DependencyFilter;
27  import org.eclipse.aether.util.filter.DependencyFilterUtils;
28  
29  import javax.inject.Inject;
30  import javax.inject.Singleton;
31  import java.io.File;
32  import java.nio.file.Path;
33  import java.util.*;
34  import java.util.concurrent.atomic.AtomicReference;
35  import java.util.function.Function;
36  import java.util.stream.Collectors;
37  import java.util.stream.Stream;
38  
39  /**
40   * Provides methods to compile a Maven project.
41   *
42   * @author Álvaro Sánchez-Mariscal
43   * @since 1.1
44   */
45  @Singleton
46  public class CompilerService {
47  
48      public static final String MAVEN_COMPILER_PLUGIN = "org.apache.maven.plugins:maven-compiler-plugin";
49      public static final String MAVEN_JAR_PLUGIN = "org.apache.maven.plugins:maven-jar-plugin";
50      public static final String MAVEN_RESOURCES_PLUGIN = "org.apache.maven.plugins:maven-resources-plugin";
51      public static final String GMAVEN_PLUS_PLUGIN = "org.codehaus.gmavenplus:gmavenplus-plugin";
52      public static final String KOTLIN_MAVEN_PLUGIN = "org.jetbrains.kotlin:kotlin-maven-plugin";
53  
54      private static final String JAVA = "java";
55      private static final String GROOVY = "groovy";
56      private static final String KOTLIN = "kotlin";
57      private static final String COMPILE_GOAL = "compile";
58      private static final String RESOURCES_GOAL = "resources";
59  
60      private final Log log;
61      private final Map<String, Path> sourceDirectories;
62      private final MavenProject mavenProject;
63      private final MavenSession mavenSession;
64      private final ExecutorService executorService;
65      private final ProjectDependenciesResolver resolver;
66      private final Invoker invoker;
67  
68      @SuppressWarnings("CdiInjectionPointsInspection")
69      @Inject
70      public CompilerService(MavenProject mavenProject, MavenSession mavenSession, ExecutorService executorService,
71                             ProjectDependenciesResolver resolver) {
72          this.resolver = resolver;
73          this.log = new SystemStreamLog();
74          this.mavenProject = mavenProject;
75          this.mavenSession = mavenSession;
76          this.executorService = executorService;
77          this.sourceDirectories = resolveSourceDirectories();
78          this.invoker = new DefaultInvoker();
79      }
80  
81      /**
82       * Compiles the project.
83       * @param copyResources whether to copy resources to the target directory.
84       * @return the last compilation time millis.
85       */
86      public Optional<Long> compileProject(boolean copyResources) {
87          Long lastCompilation = null;
88          if (log.isDebugEnabled()) {
89              log.debug("Compiling the project");
90          }
91          try {
92              if (sourceDirectories.containsKey(GROOVY)) {
93                  executorService.executeGoal(GMAVEN_PLUS_PLUGIN, "addSources");
94                  executorService.executeGoal(GMAVEN_PLUS_PLUGIN, "generateStubs");
95                  if (copyResources) {
96                      executorService.executeGoal(MAVEN_RESOURCES_PLUGIN, RESOURCES_GOAL);
97                  }
98                  executorService.executeGoal(MAVEN_COMPILER_PLUGIN, COMPILE_GOAL);
99                  executorService.executeGoal(GMAVEN_PLUS_PLUGIN, COMPILE_GOAL);
100                 executorService.executeGoal(GMAVEN_PLUS_PLUGIN, "removeStubs");
101                 lastCompilation = System.currentTimeMillis();
102             }
103             if (sourceDirectories.containsKey(KOTLIN)) {
104                 executorService.executeGoal(KOTLIN_MAVEN_PLUGIN, "kapt");
105                 if (copyResources) {
106                     executorService.executeGoal(MAVEN_RESOURCES_PLUGIN, RESOURCES_GOAL);
107                 }
108                 executorService.executeGoal(KOTLIN_MAVEN_PLUGIN, COMPILE_GOAL);
109                 executorService.executeGoal(MAVEN_COMPILER_PLUGIN, "compile#java-compile");
110                 lastCompilation = System.currentTimeMillis();
111             }
112             if (sourceDirectories.containsKey(JAVA)) {
113                 if (copyResources) {
114                     executorService.executeGoal(MAVEN_RESOURCES_PLUGIN, RESOURCES_GOAL);
115                 }
116                 executorService.executeGoal(MAVEN_COMPILER_PLUGIN, COMPILE_GOAL);
117                 lastCompilation = System.currentTimeMillis();
118             }
119         } catch (MojoExecutionException e) {
120             if (log.isErrorEnabled()) {
121                 log.error("Error while compiling the project: ", e);
122             }
123         }
124         return Optional.ofNullable(lastCompilation);
125     }
126 
127     /**
128      * Resolves the source directories by checking the existence of Java, Groovy or Kotlin sources.
129      */
130     public Map<String, Path> resolveSourceDirectories() {
131         if (log.isDebugEnabled()) {
132             log.debug("Resolving source directories...");
133         }
134         AtomicReference<String> lang = new AtomicReference<>();
135         Map<String, Path> sourceDirectoriesToResolve = Stream.of(JAVA, GROOVY, KOTLIN)
136                 .peek(lang::set)
137                 .map(l -> new File(mavenProject.getBasedir(), "src/main/" + l))
138                 .filter(File::exists)
139                 .peek(f -> {
140                     if (log.isDebugEnabled()) {
141                         log.debug("Found source: " + f.getPath());
142                     }
143                 })
144                 .map(File::toPath)
145                 .collect(Collectors.toMap(path -> lang.get(), Function.identity()));
146         if (sourceDirectoriesToResolve.isEmpty()) {
147             throw new IllegalStateException("Source folders not found for neither Java/Groovy/Kotlin");
148         }
149         return sourceDirectoriesToResolve;
150     }
151 
152     /**
153      * Resolves project dependencies for the given scopes.
154      */
155     public List<Dependency> resolveDependencies(String... scopes) {
156         try {
157             DependencyFilter filter = DependencyFilterUtils.classpathFilter(scopes);
158             RepositorySystemSession session = mavenSession.getRepositorySession();
159             DependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(mavenProject, session);
160             dependencyResolutionRequest.setResolutionFilter(filter);
161             DependencyResolutionResult result = resolver.resolve(dependencyResolutionRequest);
162             return result.getDependencies();
163         } catch (org.apache.maven.project.DependencyResolutionException e) {
164             if (log.isWarnEnabled()) {
165                 log.warn("Error while trying to resolve dependencies for the current project", e);
166             }
167             return Collections.emptyList();
168         }
169     }
170 
171     /**
172      * Builds a classpath string for the given dependencies.
173      */
174     public String buildClasspath(List<Dependency> dependencies) {
175         Comparator<Dependency> byGroupId = Comparator.comparing(d -> d.getArtifact().getGroupId());
176         Comparator<Dependency> byArtifactId = Comparator.comparing(d -> d.getArtifact().getArtifactId());
177         return dependencies.stream()
178                 .sorted(byGroupId.thenComparing(byArtifactId))
179                 .map(dependency -> dependency.getArtifact().getFile().getAbsolutePath())
180                 .collect(Collectors.joining(File.pathSeparator));
181     }
182 
183     /**
184      * Packages the project by invoking the Jar plugin.
185      */
186     public InvocationResult packageProject() throws MavenInvocationException {
187         InvocationRequest request = new DefaultInvocationRequest();
188         request.setPomFile(mavenProject.getFile());
189         request.setGoals(Collections.singletonList(MAVEN_JAR_PLUGIN + ":jar"));
190         request.setBatchMode(true);
191         request.setQuiet(true);
192         return invoker.execute(request);
193     }
194 }