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.

Runner.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Sonar Runner
  3. * Copyright (C) 2011 SonarSource
  4. * dev@sonar.codehaus.org
  5. *
  6. * This program 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. * This program 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
  17. * License along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
  19. */
  20. package org.sonar.runner;
  21. import com.google.common.annotations.VisibleForTesting;
  22. import java.io.File;
  23. import java.lang.reflect.Constructor;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;
  26. import java.net.MalformedURLException;
  27. import java.net.URL;
  28. import java.nio.charset.Charset;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import java.util.Properties;
  32. /**
  33. * <p>
  34. * Sonar Runner class that can be used to launch Sonar analyses.
  35. * </p>
  36. * <p>
  37. * Configuration is all done through properties:
  38. * </p>
  39. * <ul>
  40. * <li>"sonar.projectDir": the base directory of the project to analyse (this can also be passed via the {@link #create(Properties, File)} constructor)</li>
  41. * <li>"sonar.working.directory": the working directory, which is "${sonar.projectDir}/.sonar" by default.</li>
  42. * <li>"sonar.verbose": if set to "true", more information is displayed in the log</li>
  43. * <li>"sonar.environment.information.key" and "sonar.environment.information.version": can be used to overwrite environment information (can also be
  44. * set via {@link #setEnvironmentInformation(String, String)} method)</li>
  45. * <li>... plus all the other Sonar and Sonar plugins properties.</li>
  46. * </ul>
  47. *
  48. * @since 1.1
  49. */
  50. public final class Runner {
  51. /**
  52. * Old property used to activate debug level for logging.
  53. *
  54. * @deprecated Replaced by sonar.verbose since 1.2
  55. */
  56. @Deprecated
  57. public static final String PROPERTY_OLD_DEBUG_MODE = "runner.debug";
  58. /**
  59. * Property used to increase logging information.
  60. *
  61. * @since 1.2
  62. */
  63. public static final String PROPERTY_VERBOSE = "sonar.verbose";
  64. /**
  65. * Property used to specify the working directory for the runner. May be a relative or absolute path.
  66. *
  67. * @since 1.4
  68. */
  69. public static final String PROPERTY_WORK_DIRECTORY = "sonar.working.directory";
  70. /**
  71. * Default value of the working directory.
  72. */
  73. public static final String DEF_VALUE_WORK_DIRECTORY = ".sonar";
  74. /**
  75. * Property used to specify the base directory of the project to analyse.
  76. *
  77. * @since 1.5
  78. */
  79. public static final String PROPERTY_SONAR_PROJECT_BASEDIR = "sonar.projectBaseDir";
  80. /**
  81. * Property used to specify the name of the tool that will run a Sonar analysis.
  82. *
  83. * @since 1.5
  84. */
  85. public static final String PROPERTY_ENVIRONMENT_INFORMATION_KEY = "sonar.environment.information.key";
  86. /**
  87. * Property used to specify the version of the tool that will run a Sonar analysis.
  88. *
  89. * @since 1.5
  90. */
  91. public static final String PROPERTY_ENVIRONMENT_INFORMATION_VERSION = "sonar.environment.information.version";
  92. /**
  93. * Array of prefixes of versions of Sonar without support of this runner.
  94. */
  95. private static final String[] UNSUPPORTED_VERSIONS = {"1", "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "2.10"};
  96. private static final String[] UNSUPPORTED_VERSIONS_FOR_TASKS = {"1", "2", "3.0", "3.1", "3.2", "3.3", "3.4"};
  97. private static final String PROPERTY_SOURCE_ENCODING = "sonar.sourceEncoding";
  98. private String command;
  99. private File projectDir;
  100. private File workDir;
  101. private String[] unmaskedPackages;
  102. private List<Object> containerExtensions = new ArrayList<Object>();
  103. private Properties globalProperties;
  104. private Properties projectProperties;
  105. private boolean isEncodingPlatformDependant;
  106. private Runner(String command, Properties globalProperties, Properties projectProperties) {
  107. this.command = command;
  108. this.globalProperties = globalProperties;
  109. this.projectProperties = projectProperties;
  110. this.unmaskedPackages = new String[0];
  111. // set the default values for the Sonar Runner - they can be overriden with #setEnvironmentInformation
  112. this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_KEY, "Runner");
  113. this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_VERSION, Version.getVersion());
  114. // sets the encoding if not forced
  115. if (!globalProperties.containsKey(PROPERTY_SOURCE_ENCODING) && !projectProperties.containsKey(PROPERTY_SOURCE_ENCODING)) {
  116. isEncodingPlatformDependant = true;
  117. globalProperties.setProperty(PROPERTY_SOURCE_ENCODING, Charset.defaultCharset().name());
  118. }
  119. // and init the directories
  120. initDirs();
  121. }
  122. /**
  123. * Creates a Runner based only on the given properties.
  124. * @deprecated Use {@link Runner#create(String, Properties, Properties)}
  125. */
  126. @Deprecated
  127. public static Runner create(Properties props) {
  128. return create(null, new Properties(), props);
  129. }
  130. /**
  131. * Creates a Runner based only on the given properties.
  132. */
  133. public static Runner create(String command, Properties globalProperties, Properties projectProperties) {
  134. return new Runner(command, globalProperties, projectProperties);
  135. }
  136. /**
  137. * Creates a Runner based only on the properties and with the given base directory.
  138. * @deprecated Use {@link Runner#create(String, Properties, Properties, File)}
  139. */
  140. @Deprecated
  141. public static Runner create(Properties props, File basedir) {
  142. return create(null, new Properties(), props, basedir);
  143. }
  144. /**
  145. * Creates a Runner based only on the properties and with the given base directory.
  146. */
  147. public static Runner create(String command, Properties globalProperties, Properties projectProperties, File basedir) {
  148. projectProperties.put(PROPERTY_SONAR_PROJECT_BASEDIR, basedir.getAbsolutePath());
  149. return new Runner(command, globalProperties, projectProperties);
  150. }
  151. /**
  152. * Runs a Sonar analysis.
  153. */
  154. public void execute() {
  155. Bootstrapper bootstrapper = new Bootstrapper("SonarRunner/" + Version.getVersion(), getSonarServerURL(), getWorkDir());
  156. checkSonarVersion(bootstrapper);
  157. delegateExecution(createClassLoader(bootstrapper));
  158. }
  159. public String getSonarServerURL() {
  160. return projectProperties.getProperty("sonar.host.url", globalProperties.getProperty("sonar.host.url", "http://localhost:9000"));
  161. }
  162. private void initDirs() {
  163. String path = projectProperties.getProperty(PROPERTY_SONAR_PROJECT_BASEDIR, ".");
  164. projectDir = new File(path);
  165. if (!projectDir.isDirectory()) {
  166. throw new RunnerException("Project home must be an existing directory: " + path);
  167. }
  168. // project home exists: add its absolute path as "sonar.projectBaseDir" property
  169. projectProperties.put(PROPERTY_SONAR_PROJECT_BASEDIR, projectDir.getAbsolutePath());
  170. workDir = initWorkDir();
  171. }
  172. private File initWorkDir() {
  173. File newWorkDir;
  174. String customWorkDir = projectProperties.getProperty(PROPERTY_WORK_DIRECTORY, globalProperties.getProperty(PROPERTY_WORK_DIRECTORY));
  175. if (customWorkDir == null || "".equals(customWorkDir.trim())) {
  176. newWorkDir = new File(getProjectDir(), DEF_VALUE_WORK_DIRECTORY);
  177. }
  178. else {
  179. newWorkDir = defineCustomizedWorkDir(new File(customWorkDir));
  180. }
  181. IOUtils.deleteQuietly(newWorkDir);
  182. return newWorkDir;
  183. }
  184. private File defineCustomizedWorkDir(File customWorkDir) {
  185. if (customWorkDir.isAbsolute()) {
  186. return customWorkDir;
  187. }
  188. return new File(getProjectDir(), customWorkDir.getPath());
  189. }
  190. /**
  191. * @return the project base directory
  192. */
  193. public File getProjectDir() {
  194. return projectDir;
  195. }
  196. /**
  197. * @return work directory, default is ".sonar" in project directory
  198. */
  199. public File getWorkDir() {
  200. return workDir;
  201. }
  202. /**
  203. * @return the source code encoding that will be used by Sonar
  204. */
  205. public String getSourceCodeEncoding() {
  206. return projectProperties.getProperty(PROPERTY_SOURCE_ENCODING, globalProperties.getProperty(PROPERTY_SOURCE_ENCODING));
  207. }
  208. /**
  209. * @return true if the property "sonar.sourceEncoding" hasn't been forced
  210. */
  211. public boolean isEncodingPlatformDependant() {
  212. return isEncodingPlatformDependant;
  213. }
  214. public String getCommand() {
  215. return command;
  216. }
  217. /**
  218. * @return global properties, project properties and command-line properties
  219. */
  220. @VisibleForTesting
  221. public Properties getProperties() {
  222. Properties props = new Properties();
  223. props.putAll(globalProperties);
  224. props.putAll(projectProperties);
  225. return props;
  226. }
  227. protected void checkSonarVersion(Bootstrapper bootstrapper) {
  228. String serverVersion = bootstrapper.getServerVersion();
  229. if (isUnsupportedVersion(serverVersion)) {
  230. throw new RunnerException("Sonar " + serverVersion
  231. + " is not supported. Please upgrade Sonar to version 2.11 or more.");
  232. }
  233. if (command != null && isUnsupportedVersionForTasks(serverVersion)) {
  234. throw new RunnerException("Sonar " + serverVersion
  235. + " doesn't support tasks. Please upgrade Sonar to version 3.5 or more.");
  236. }
  237. }
  238. private BootstrapClassLoader createClassLoader(Bootstrapper bootstrapper) {
  239. URL url = getJarPath();
  240. return bootstrapper.createClassLoader(
  241. // Add JAR with Sonar Runner - it's a Jar which contains this class
  242. new URL[] {url},
  243. getClass().getClassLoader(),
  244. unmaskedPackages);
  245. }
  246. /**
  247. * For unknown reasons <code>getClass().getProtectionDomain().getCodeSource().getLocation()</code> doesn't work under Ant 1.7.0.
  248. * So this is a workaround.
  249. *
  250. * @return Jar which contains this class
  251. */
  252. public static URL getJarPath() {
  253. String pathToClass = "/" + Runner.class.getName().replace('.', '/') + ".class";
  254. URL url = Runner.class.getResource(pathToClass);
  255. if (url != null) {
  256. String path = url.toString();
  257. String uri = null;
  258. if (path.startsWith("jar:file:")) {
  259. int bang = path.indexOf('!');
  260. uri = path.substring(4, bang);
  261. } else if (path.startsWith("file:")) {
  262. int tail = path.indexOf(pathToClass);
  263. uri = path.substring(0, tail);
  264. }
  265. if (uri != null) {
  266. try {
  267. return new URL(uri);
  268. } catch (MalformedURLException e) {
  269. }
  270. }
  271. }
  272. return null;
  273. }
  274. static boolean isUnsupportedVersion(String version) {
  275. for (String unsupportedVersion : UNSUPPORTED_VERSIONS) {
  276. if (isVersion(version, unsupportedVersion)) {
  277. return true;
  278. }
  279. }
  280. return false;
  281. }
  282. static boolean isUnsupportedVersionForTasks(String version) {
  283. for (String unsupportedVersion : UNSUPPORTED_VERSIONS_FOR_TASKS) {
  284. if (isVersion(version, unsupportedVersion)) {
  285. return true;
  286. }
  287. }
  288. return false;
  289. }
  290. static boolean isVersion(String version, String prefix) {
  291. return version.startsWith(prefix + ".") || version.equals(prefix);
  292. }
  293. private void delegateExecution(BootstrapClassLoader sonarClassLoader) {
  294. ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
  295. try {
  296. Thread.currentThread().setContextClassLoader(sonarClassLoader);
  297. Class<?> launcherClass = sonarClassLoader.findClass("org.sonar.runner.internal.batch.Launcher");
  298. Constructor<?> constructor = launcherClass.getConstructor(String.class, Properties.class, Properties.class, List.class);
  299. Object launcher = constructor.newInstance(getCommand(), globalProperties, projectProperties, containerExtensions);
  300. Method method = launcherClass.getMethod("execute");
  301. method.invoke(launcher);
  302. } catch (InvocationTargetException e) {
  303. // Unwrap original exception
  304. throw new RunnerException("Unable to execute Sonar", e.getTargetException());
  305. } catch (Exception e) {
  306. // Catch all other exceptions, which relates to reflection
  307. throw new RunnerException("Unable to execute Sonar", e);
  308. } finally {
  309. Thread.currentThread().setContextClassLoader(oldContextClassLoader);
  310. }
  311. }
  312. /**
  313. * Allows to overwrite the environment information when Sonar Runner is embedded in a specific tool (for instance, with the Sonar Ant Task).
  314. *
  315. * @param key the key of the tool that embeds Sonar Runner
  316. * @param version the version of this tool
  317. */
  318. public void setEnvironmentInformation(String key, String version) {
  319. this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_KEY, key);
  320. this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_VERSION, version);
  321. }
  322. public void setUnmaskedPackages(String... unmaskedPackages) {
  323. this.unmaskedPackages = unmaskedPackages;
  324. }
  325. public void addContainerExtension(Object extension) {
  326. containerExtensions.add(extension);
  327. }
  328. }