You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MavenProjectConverter.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * SonarQube, open source software quality management tool.
  3. * Copyright (C) 2008-2014 SonarSource
  4. * mailto:contact AT sonarsource DOT com
  5. *
  6. * SonarQube is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * SonarQube is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.batch.maven;
  21. import com.google.common.annotations.VisibleForTesting;
  22. import com.google.common.base.Function;
  23. import com.google.common.base.Predicate;
  24. import com.google.common.collect.Collections2;
  25. import com.google.common.collect.Lists;
  26. import com.google.common.collect.Maps;
  27. import org.apache.commons.lang.StringUtils;
  28. import org.apache.maven.model.CiManagement;
  29. import org.apache.maven.model.IssueManagement;
  30. import org.apache.maven.model.Scm;
  31. import org.apache.maven.project.MavenProject;
  32. import org.sonar.api.CoreProperties;
  33. import org.sonar.api.batch.SupportedEnvironment;
  34. import org.sonar.api.batch.bootstrap.ProjectDefinition;
  35. import org.sonar.api.batch.maven.MavenUtils;
  36. import org.sonar.api.task.TaskExtension;
  37. import org.sonar.api.utils.MessageException;
  38. import org.sonar.java.api.JavaUtils;
  39. import javax.annotation.Nonnull;
  40. import javax.annotation.Nullable;
  41. import java.io.File;
  42. import java.io.IOException;
  43. import java.util.Arrays;
  44. import java.util.Collection;
  45. import java.util.List;
  46. import java.util.Map;
  47. /**
  48. * @deprecated since 4.3 kept only to support old version of SonarQube Mojo
  49. */
  50. @Deprecated
  51. @SupportedEnvironment("maven")
  52. public class MavenProjectConverter implements TaskExtension {
  53. private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." +
  54. " Probably you use Maven Advanced Reactor Options, which is not supported by SonarQube and should not be used.";
  55. public ProjectDefinition configure(List<MavenProject> poms, MavenProject root) {
  56. // projects by canonical path to pom.xml
  57. Map<String, MavenProject> paths = Maps.newHashMap();
  58. Map<MavenProject, ProjectDefinition> defs = Maps.newHashMap();
  59. try {
  60. configureModules(poms, paths, defs);
  61. rebuildModuleHierarchy(paths, defs);
  62. } catch (IOException e) {
  63. throw new IllegalStateException("Cannot configure project", e);
  64. }
  65. ProjectDefinition rootProject = defs.get(root);
  66. if (rootProject == null) {
  67. throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
  68. }
  69. return rootProject;
  70. }
  71. private void rebuildModuleHierarchy(Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
  72. for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
  73. MavenProject pom = entry.getValue();
  74. for (Object m : pom.getModules()) {
  75. String moduleId = (String) m;
  76. File modulePath = new File(pom.getBasedir(), moduleId);
  77. MavenProject module = findMavenProject(modulePath, paths);
  78. ProjectDefinition parentProject = defs.get(pom);
  79. if (parentProject == null) {
  80. throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
  81. }
  82. ProjectDefinition subProject = defs.get(module);
  83. if (subProject == null) {
  84. throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
  85. }
  86. parentProject.addSubProject(subProject);
  87. }
  88. }
  89. }
  90. private void configureModules(List<MavenProject> poms, Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
  91. for (MavenProject pom : poms) {
  92. paths.put(pom.getFile().getCanonicalPath(), pom);
  93. ProjectDefinition def = ProjectDefinition.create();
  94. merge(pom, def);
  95. defs.put(pom, def);
  96. }
  97. }
  98. private static MavenProject findMavenProject(final File modulePath, Map<String, MavenProject> paths) throws IOException {
  99. if (modulePath.exists() && modulePath.isDirectory()) {
  100. for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
  101. String pomFileParentDir = new File(entry.getKey()).getParent();
  102. if (pomFileParentDir.equals(modulePath.getCanonicalPath())) {
  103. return entry.getValue();
  104. }
  105. }
  106. return null;
  107. }
  108. return paths.get(modulePath.getCanonicalPath());
  109. }
  110. @VisibleForTesting
  111. void merge(MavenProject pom, ProjectDefinition definition) {
  112. String key = getSonarKey(pom);
  113. // IMPORTANT NOTE : reference on properties from POM model must not be saved,
  114. // instead they should be copied explicitly - see SONAR-2896
  115. definition
  116. .setProperties(pom.getModel().getProperties())
  117. .setKey(key)
  118. .setVersion(pom.getVersion())
  119. .setName(pom.getName())
  120. .setDescription(pom.getDescription())
  121. .addContainerExtension(pom);
  122. guessJavaVersion(pom, definition);
  123. guessEncoding(pom, definition);
  124. convertMavenLinksToProperties(definition, pom);
  125. synchronizeFileSystem(pom, definition);
  126. }
  127. private static String getSonarKey(MavenProject pom) {
  128. return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString();
  129. }
  130. private static void guessEncoding(MavenProject pom, ProjectDefinition definition) {
  131. // See http://jira.sonarsource.com/browse/SONAR-2151
  132. String encoding = MavenUtils.getSourceEncoding(pom);
  133. if (encoding != null) {
  134. definition.setProperty(CoreProperties.ENCODING_PROPERTY, encoding);
  135. }
  136. }
  137. private static void guessJavaVersion(MavenProject pom, ProjectDefinition definition) {
  138. // See http://jira.sonarsource.com/browse/SONAR-2148
  139. // Get Java source and target versions from maven-compiler-plugin.
  140. String version = MavenUtils.getJavaSourceVersion(pom);
  141. if (version != null) {
  142. definition.setProperty(JavaUtils.JAVA_SOURCE_PROPERTY, version);
  143. }
  144. version = MavenUtils.getJavaVersion(pom);
  145. if (version != null) {
  146. definition.setProperty(JavaUtils.JAVA_TARGET_PROPERTY, version);
  147. }
  148. }
  149. /**
  150. * For SONAR-3676
  151. */
  152. private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) {
  153. setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl());
  154. Scm scm = pom.getScm();
  155. if (scm == null) {
  156. scm = new Scm();
  157. }
  158. setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl());
  159. setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection());
  160. CiManagement ci = pom.getCiManagement();
  161. if (ci == null) {
  162. ci = new CiManagement();
  163. }
  164. setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl());
  165. IssueManagement issues = pom.getIssueManagement();
  166. if (issues == null) {
  167. issues = new IssueManagement();
  168. }
  169. setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl());
  170. }
  171. private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) {
  172. if (StringUtils.isBlank(definition.properties().get(propertyKey))) {
  173. definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue));
  174. }
  175. }
  176. public void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) {
  177. into.setBaseDir(pom.getBasedir());
  178. File buildDir = getBuildDir(pom);
  179. if (buildDir != null) {
  180. into.setBuildDir(buildDir);
  181. into.setWorkDir(getSonarWorkDir(pom));
  182. }
  183. into.setSourceDirs(toPaths(mainDirs(pom)));
  184. into.setTestDirs(toPaths(testDirs(pom)));
  185. File binaryDir = resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir());
  186. if (binaryDir != null) {
  187. into.addBinaryDir(binaryDir);
  188. }
  189. }
  190. public static File getSonarWorkDir(MavenProject pom) {
  191. return new File(getBuildDir(pom), "sonar");
  192. }
  193. private static File getBuildDir(MavenProject pom) {
  194. return resolvePath(pom.getBuild().getDirectory(), pom.getBasedir());
  195. }
  196. static File resolvePath(@Nullable String path, File basedir) {
  197. if (path != null) {
  198. File file = new File(StringUtils.trim(path));
  199. if (!file.isAbsolute()) {
  200. try {
  201. file = new File(basedir, path).getCanonicalFile();
  202. } catch (IOException e) {
  203. throw new IllegalStateException("Unable to resolve path '" + path + "'", e);
  204. }
  205. }
  206. return file;
  207. }
  208. return null;
  209. }
  210. static List<File> resolvePaths(List<String> paths, File basedir) {
  211. List<File> result = Lists.newArrayList();
  212. for (String path : paths) {
  213. File dir = resolvePath(path, basedir);
  214. if (dir != null) {
  215. result.add(dir);
  216. }
  217. }
  218. return result;
  219. }
  220. private List<File> mainDirs(MavenProject pom) {
  221. return sourceDirs(pom, ProjectDefinition.SOURCE_DIRS_PROPERTY, pom.getCompileSourceRoots());
  222. }
  223. private List<File> testDirs(MavenProject pom) {
  224. return sourceDirs(pom, ProjectDefinition.TEST_DIRS_PROPERTY, pom.getTestCompileSourceRoots());
  225. }
  226. private List<File> sourceDirs(MavenProject pom, String propertyKey, List mavenDirs) {
  227. List<String> paths;
  228. String prop = pom.getProperties().getProperty(propertyKey);
  229. if (prop != null) {
  230. paths = Arrays.asList(StringUtils.split(prop, ","));
  231. // do not remove dirs that do not exist. They must be kept in order to
  232. // notify users that value of sonar.sources has a typo.
  233. return existingDirsOrFail(resolvePaths(paths, pom.getBasedir()), pom, propertyKey);
  234. }
  235. List<File> dirs = resolvePaths(mavenDirs, pom.getBasedir());
  236. // Maven provides some directories that do not exist. They
  237. // should be removed
  238. return keepExistingDirs(dirs);
  239. }
  240. private List<File> existingDirsOrFail(List<File> dirs, MavenProject pom, String propertyKey) {
  241. for (File dir : dirs) {
  242. if (!dir.isDirectory() || !dir.exists()) {
  243. throw MessageException.of(String.format(
  244. "The directory '%s' does not exist for Maven module %s. Please check the property %s",
  245. dir.getAbsolutePath(), pom.getId(), propertyKey));
  246. }
  247. }
  248. return dirs;
  249. }
  250. private static List<File> keepExistingDirs(List<File> files) {
  251. return Lists.newArrayList(Collections2.filter(files, new Predicate<File>() {
  252. @Override
  253. public boolean apply(File dir) {
  254. return dir != null && dir.exists() && dir.isDirectory();
  255. }
  256. }));
  257. }
  258. private static String[] toPaths(Collection<File> dirs) {
  259. Collection<String> paths = Collections2.transform(dirs, new Function<File, String>() {
  260. @Override
  261. public String apply(@Nonnull File dir) {
  262. return dir.getAbsolutePath();
  263. }
  264. });
  265. return paths.toArray(new String[paths.size()]);
  266. }
  267. }