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

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