Browse Source

SONARPLUGINS-1220 Runner home must be optional

tags/2.5-rc1
Simon Brandhof 13 years ago
parent
commit
9132e3292b

+ 9
- 9
src/main/java/org/sonar/runner/Launcher.java View File

@@ -39,14 +39,14 @@ import org.sonar.batch.bootstrapper.Reactor;

public class Launcher {

private Main task;
private Runner runner;

public Launcher(Main launcher) {
this.task = launcher;
public Launcher(Runner runner) {
this.runner = runner;
}

/**
* This method invoked from {@link Main}.
* This method invoked from {@link Main}. Do not rename it.
*/
public void execute() {
initLogging();
@@ -56,7 +56,7 @@ public class Launcher {
private void executeBatch() {
ProjectDefinition project = defineProject();
Reactor reactor = new Reactor(project);
Batch batch = new Batch(getInitialConfiguration(project), new EnvironmentInformation("Runner", Main.getRunnerVersion()), reactor);
Batch batch = new Batch(getInitialConfiguration(project), new EnvironmentInformation("Runner", runner.getRunnerVersion()), reactor);
batch.execute();
}

@@ -66,7 +66,7 @@ public class Launcher {
jc.setContext(context);
context.reset();
InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml");
System.setProperty("ROOT_LOGGER_LEVEL", task.isDebugEnabled() ? "DEBUG" : "INFO");
System.setProperty("ROOT_LOGGER_LEVEL", runner.isDebug() ? "DEBUG" : "INFO");
try {
jc.doConfigure(input);

@@ -79,9 +79,9 @@ public class Launcher {
}

private ProjectDefinition defineProject() {
File baseDir = task.getProjectDir();
Properties properties = task.getProperties();
ProjectDefinition definition = new ProjectDefinition(baseDir, task.getWorkDir(), properties);
File baseDir = runner.getProjectDir();
Properties properties = runner.getProperties();
ProjectDefinition definition = new ProjectDefinition(baseDir, runner.getWorkDir(), properties);
for (String dir : getList(properties, "sources")) {
definition.addSourceDir(dir);
}

+ 70
- 139
src/main/java/org/sonar/runner/Main.java View File

@@ -20,175 +20,106 @@

package org.sonar.runner;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.sonar.batch.bootstrapper.BootstrapClassLoader;
import org.sonar.batch.bootstrapper.BootstrapException;
import org.sonar.batch.bootstrapper.Bootstrapper;
import org.sonar.batch.bootstrapper.BootstrapperIOUtils;

public class Main {

private static boolean debug = false;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;

private File workDir;
private Properties properties = new Properties();
private Bootstrapper bootstrapper;
/**
* Arguments :
* <ul>
* <li>runner.home: optional path to runner home (root directory with sub-directories bin, lib and conf)</li>
* <li>runner.settings: optional path to runner global settings, usually ${runner.home}/conf/sonar-runner.properties. This property is used only if ${runner.home} is not defined</li>
* <li>project.home: path to project root directory. If not set, then it's supposed to be the directory where the runner is executed</li>
* <li>project.settings: optional path to project settings. Default value is ${project.home}/sonar-project.properties.</li>
* </ul>
*
* @since 1.0
*/
public final class Main {

public static void main(String[] args) {
log("Sonar Standalone Runner version: " + getRunnerVersion());
Map<String, String> cmdProps = parseArguments(args);
new Main().execute(cmdProps);
}

public void execute(Map<String, String> cmdProps) {
String home = System.getProperty("runner.home");
// Load global configuration
loadProperties(new File(home + "/conf/sonar-runner.properties"));
// Load project configuration
loadProperties(new File(getProjectDir(), "sonar-project.properties"));
// Load properties from command-line
for (Map.Entry<String, String> entry : cmdProps.entrySet()) {
properties.setProperty(entry.getKey(), entry.getValue());
Properties props = loadProperties(args);
Runner runner = Runner.create(props);
log("Runner version: " + runner.getRunnerVersion());
log("Server: " + runner.getServerURl());
log("Work directory: " + runner.getWorkDir().getAbsolutePath());
runner.execute();
}

static Properties loadProperties(String[] args) {
Properties props = new Properties();
Properties commandLineProps = parseArguments(args);
props.putAll(loadRunnerProperties(commandLineProps));
props.putAll(loadProjectProperties(commandLineProps));
props.putAll(commandLineProps);
return props;
}

static Properties loadRunnerProperties(Properties props) {
File settingsFile = locatePropertiesFile(props, "runner.home", "conf/sonar-runner.properties", "runner.settings");
if (settingsFile != null && settingsFile.isFile() && settingsFile.exists()) {
log("Runner settings: " + settingsFile.getAbsolutePath());
return toProperties(settingsFile);
}

String serverUrl = properties.getProperty("sonar.host.url", "http://localhost:9000");
log("Sonar server: " + serverUrl);
log("Sonar work directory: " + getWorkDir().getAbsolutePath());
bootstrapper = new Bootstrapper("SonarRunner/" + getRunnerVersion(), serverUrl, getWorkDir());
checkSonarVersion();
delegateExecution(createClassLoader());
}

/**
* @return project directory (current directory)
*/
public File getProjectDir() {
return new File(".").getAbsoluteFile();
return new Properties();
}

/**
* @return work directory, default is ".sonar" in project directory
*/
public File getWorkDir() {
if (workDir == null) {
workDir = new File(getProjectDir(), ".sonar");
static Properties loadProjectProperties(Properties props) {
File settingsFile = locatePropertiesFile(props, "project.home", "sonar-project.properties", "project.settings");
if (settingsFile.isFile() && settingsFile.exists()) {
log("Project settings: " + settingsFile.getAbsolutePath());
return toProperties(settingsFile);
}
return workDir;
return new Properties();
}

/**
* @return global properties, project properties and command-line properties
*/
public Properties getProperties() {
return properties;
}

public boolean isDebugEnabled() {
return debug;
}
private static File locatePropertiesFile(Properties props, String homeKey, String relativePathFromHome, String settingsKey) {
File settingsFile = null;
String runnerHome = props.getProperty(homeKey);
if (runnerHome != null && !runnerHome.equals("")) {
settingsFile = new File(runnerHome, relativePathFromHome);
}

/**
* Loads {@link Launcher} from specified {@link BootstrapClassLoader} and passes control to it.
*
* @see Launcher#execute()
*/
private void delegateExecution(BootstrapClassLoader sonarClassLoader) {
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(sonarClassLoader);
Class<?> launcherClass = sonarClassLoader.findClass("org.sonar.runner.Launcher");
Constructor<?> constructor = launcherClass.getConstructor(Main.class);
Object launcher = constructor.newInstance(this);
Method method = launcherClass.getMethod("execute");
method.invoke(launcher);
} catch (InvocationTargetException e) {
// Unwrap original exception
throw new BootstrapException(e.getTargetException());
} catch (Exception e) {
// Catch all other exceptions, which relates to reflection
throw new BootstrapException(e);
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
if (settingsFile == null || !settingsFile.exists()) {
String settingsPath = props.getProperty(settingsKey);
if (settingsPath != null && !settingsPath.equals("")) {
settingsFile = new File(settingsPath);
}
}
return settingsFile;
}

/**
* Loads properties from specified file.
*/
private void loadProperties(File file) {

private static Properties toProperties(File file) {
InputStream in = null;
Properties properties = new Properties();
try {
in = new FileInputStream(file);
properties.load(in);
} catch (FileNotFoundException e) {
throw new BootstrapException(e);
} catch (IOException e) {
throw new BootstrapException(e);
} finally {
BootstrapperIOUtils.closeQuietly(in);
}
}

private void checkSonarVersion() {
String serverVersion = bootstrapper.getServerVersion();
log("Sonar version: " + serverVersion);
if (isVersionPriorTo2Dot6(serverVersion)) {
throw new BootstrapException("Sonar " + serverVersion
+ " does not support Standalone Runner. Please upgrade Sonar to version 2.6 or more.");
}
}

private BootstrapClassLoader createClassLoader() {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
return bootstrapper.createClassLoader(
new URL[] { url }, // Add JAR with Sonar Runner - it's a Jar which contains this class
getClass().getClassLoader());
}

static boolean isVersionPriorTo2Dot6(String version) {
return isVersion(version, "1")
|| isVersion(version, "2.0")
|| isVersion(version, "2.1")
|| isVersion(version, "2.2")
|| isVersion(version, "2.3")
|| isVersion(version, "2.4")
|| isVersion(version, "2.5");
}
return properties;

private static boolean isVersion(String version, String prefix) {
return version.startsWith(prefix + ".") || version.equals(prefix);
}
} catch (Exception e) {
throw new BootstrapException(e);

public static String getRunnerVersion() {
InputStream in = null;
try {
in = Main.class.getResourceAsStream("/org/sonar/runner/version.txt");
Properties props = new Properties();
props.load(in);
return props.getProperty("version");
} catch (IOException e) {
throw new BootstrapException("Could not load the version information for Sonar Standalone Runner", e);
} finally {
BootstrapperIOUtils.closeQuietly(in);
}
}

private static Map<String, String> parseArguments(String[] args) {
HashMap<String, String> cmdProps = new HashMap<String, String>();
static Properties parseArguments(String[] args) {
Properties props = new Properties();
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if ("-h".equals(arg) || "--help".equals(arg)) {
printUsage();

} else if ("-X".equals(arg) || "--debug".equals(arg)) {
debug = true;
props.setProperty(Runner.DEBUG_MODE, "true");

} else if ("-D".equals(arg) || "--define".equals(arg)) {
i++;
if (i >= args.length) {
@@ -204,12 +135,12 @@ public class Main {
key = arg.substring(0, j);
value = arg.substring(j + 1);
}
cmdProps.put(key, value);
props.setProperty(key, value);
} else {
printError("Unrecognized option: " + arg);
}
}
return cmdProps;
return props;
}

private static void printUsage() {

+ 163
- 0
src/main/java/org/sonar/runner/Runner.java View File

@@ -0,0 +1,163 @@
/*
* Sonar Standalone Runner
* Copyright (C) 2011 SonarSource
* dev@sonar.codehaus.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.runner;

import org.sonar.batch.bootstrapper.BootstrapClassLoader;
import org.sonar.batch.bootstrapper.BootstrapException;
import org.sonar.batch.bootstrapper.Bootstrapper;
import org.sonar.batch.bootstrapper.BootstrapperIOUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Properties;

/**
* @since 1.1
*/
public final class Runner {
public static final String DEBUG_MODE = "runner.debug";

private File projectDir;
private File workDir;
private Properties properties;

private Runner(Properties props) {
this.properties = props;
initDirs();
}

public static Runner create(Properties props) {
return new Runner(props);
}

public void execute() {
Bootstrapper bootstrapper = new Bootstrapper("SonarRunner/" + getRunnerVersion(), getServerURl(), getWorkDir());
checkSonarVersion(bootstrapper);
delegateExecution(createClassLoader(bootstrapper));
}

public String getServerURl() {
return properties.getProperty("sonar.host.url", "http://localhost:9000");
}

private void initDirs() {
String path = properties.getProperty("project.home", ".");
projectDir = new File(path);
if (!projectDir.isDirectory() || !projectDir.exists()) {
throw new IllegalArgumentException("Project home must be an existing directory: " + path);
}
workDir = new File(projectDir, ".sonar");
}

public File getProjectDir() {
return projectDir;
}

/**
* @return work directory, default is ".sonar" in project directory
*/
public File getWorkDir() {
return workDir;
}

/**
* @return global properties, project properties and command-line properties
*/
public Properties getProperties() {
return properties;
}

public boolean isDebug() {
return Boolean.parseBoolean(properties.getProperty(DEBUG_MODE, "false"));
}

public String getRunnerVersion() {
InputStream in = null;
try {
in = Runner.class.getResourceAsStream("/org/sonar/runner/version.txt");
Properties props = new Properties();
props.load(in);
return props.getProperty("version");
} catch (IOException e) {
throw new BootstrapException("Could not load the version information for Sonar Standalone Runner", e);
} finally {
BootstrapperIOUtils.closeQuietly(in);
}
}

private void checkSonarVersion(Bootstrapper bootstrapper) {
String serverVersion = bootstrapper.getServerVersion();
if (isVersionPriorTo2Dot6(serverVersion)) {
throw new BootstrapException("Sonar " + serverVersion
+ " does not support Standalone Runner. Please upgrade Sonar to version 2.6 or more.");
}
}

private BootstrapClassLoader createClassLoader(Bootstrapper bootstrapper) {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
return bootstrapper.createClassLoader(
new URL[]{url}, // Add JAR with Sonar Runner - it's a Jar which contains this class
getClass().getClassLoader());
}

static boolean isVersionPriorTo2Dot6(String version) {
return isVersion(version, "1")
|| isVersion(version, "2.0")
|| isVersion(version, "2.1")
|| isVersion(version, "2.2")
|| isVersion(version, "2.3")
|| isVersion(version, "2.4")
|| isVersion(version, "2.5");
}

static boolean isVersion(String version, String prefix) {
return version.startsWith(prefix + ".") || version.equals(prefix);
}

/**
* Loads {@link Launcher} from specified {@link org.sonar.batch.bootstrapper.BootstrapClassLoader} and passes control to it.
*
* @see Launcher#execute()
*/
private void delegateExecution(BootstrapClassLoader sonarClassLoader) {
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(sonarClassLoader);
Class<?> launcherClass = sonarClassLoader.findClass("org.sonar.runner.Launcher");
Constructor<?> constructor = launcherClass.getConstructor(Runner.class);
Object launcher = constructor.newInstance(this);
Method method = launcherClass.getMethod("execute");
method.invoke(launcher);
} catch (InvocationTargetException e) {
// Unwrap original exception
throw new BootstrapException(e.getTargetException());
} catch (Exception e) {
// Catch all other exceptions, which relates to reflection
throw new BootstrapException(e);
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
}
}
}

+ 66
- 16
src/test/java/org/sonar/runner/MainTest.java View File

@@ -22,31 +22,81 @@ package org.sonar.runner;

import org.junit.Test;

import static org.hamcrest.Matchers.containsString;
import java.io.File;
import java.util.Properties;

import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;

public class MainTest {

@Test
public void shouldCheckVersion() {
assertThat(Main.isVersionPriorTo2Dot6("1.0"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.0"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.1"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.2"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.3"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.4"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.4.1"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.5"), is(true));
assertThat(Main.isVersionPriorTo2Dot6("2.6"), is(false));
public void shouldParseEmptyArguments() {
Properties props = Main.parseArguments(new String[]{});
assertThat(props.isEmpty(), is(true));
}

@Test
public void shouldParseArguments() {
Properties props = Main.parseArguments(new String[]{"-D", "foo=bar", "--define", "hello=world"});
assertThat(props.size(), is(2));
assertThat(props.getProperty("foo"), is("bar"));
assertThat(props.getProperty("hello"), is("world"));
}

@Test
public void shouldEnableDebugMode() {
Properties props = Main.parseArguments(new String[]{"-X"});
assertThat(props.getProperty(Runner.DEBUG_MODE), is("true"));
}

@Test
public void shouldDisableDebugModeByDefault() {
Properties props = Main.parseArguments(new String[]{});
assertThat(props.getProperty(Runner.DEBUG_MODE), nullValue());
}

@Test
public void shouldGetVersion() {
String version = Main.getRunnerVersion();
assertThat(version, containsString("."));
assertThat(version, not(containsString("$")));
public void shouldLoadRunnerSettingsByHome() throws Exception {
File home = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByHome/").toURI());
Properties args = new Properties();
args.setProperty("runner.home", home.getCanonicalPath());

Properties props = Main.loadRunnerProperties(args);

assertThat(props.getProperty("sonar.host.url"), is("http://moon/sonar"));
}

@Test
public void shouldNotFailIfNoHome() throws Exception {
Properties args = new Properties();
Properties props = Main.loadRunnerProperties(args);

assertThat(props.isEmpty(), is(true));
}

@Test
public void shouldLoadRunnerSettingsByDirectPath() throws Exception {
File settings = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByDirectPath/other-conf.properties").toURI());
Properties args = new Properties();
args.setProperty("runner.settings", settings.getCanonicalPath());
Properties props = Main.loadRunnerProperties(args);

assertThat(props.getProperty("sonar.host.url"), is("http://other/sonar"));
}

@Test
public void shouldLoadCompleteConfiguration() throws Exception {
File runnerHome = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/runner").toURI());
File projectHome = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/project").toURI());
Properties props = Main.loadProperties(new String[]{
"-D", "runner.home=" + runnerHome.getCanonicalPath(),
"-D", "project.home=" + projectHome.getCanonicalPath()
});

assertThat(props.getProperty("project.key"), is("foo"));
assertThat(props.getProperty("sonar.host.url"), is("http://overridden/sonar"));
assertThat(props.getProperty("sonar.jdbc.url"), is("jdbc:mysql:localhost/sonar"));
}
}

+ 77
- 0
src/test/java/org/sonar/runner/RunnerTest.java View File

@@ -0,0 +1,77 @@
/*
* Sonar Standalone Runner
* Copyright (C) 2011 SonarSource
* dev@sonar.codehaus.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/

package org.sonar.runner;

import org.junit.Test;

import java.io.File;
import java.net.URISyntaxException;
import java.util.Properties;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;

public class RunnerTest {
@Test
public void shouldCheckVersion() {
assertThat(Runner.isVersionPriorTo2Dot6("1.0"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.0"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.1"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.2"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.3"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.4"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.4.1"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.5"), is(true));
assertThat(Runner.isVersionPriorTo2Dot6("2.6"), is(false));
}

/**
* This test can only be executed by Maven, not by IDE
*/
@Test
public void shouldGetVersion() {
String version = Runner.create(new Properties()).getRunnerVersion();
assertThat(version.length(), greaterThanOrEqualTo(3));
assertThat(version, containsString("."));

// test that version is set by Maven build
assertThat(version, not(containsString("$")));
}

@Test
public void shouldInitDirs() throws Exception {
Properties props = new Properties();
File home = new File(getClass().getResource("/org/sonar/runner/RunnerTest/shouldInitDirs/").toURI());
props.setProperty("project.home", home.getCanonicalPath());
Runner runner = Runner.create(props);

assertThat(runner.getProjectDir(), is(home));
assertThat(runner.getWorkDir(), is(new File(home, ".sonar")));
}

@Test
public void shouldInitProjectDirWithCurrentDir() throws Exception {
Runner runner = Runner.create(new Properties());

assertThat(runner.getProjectDir().isDirectory(), is(true));
assertThat(runner.getProjectDir().exists(), is(true));
}
}

+ 4
- 0
src/test/resources/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/project/sonar-project.properties View File

@@ -0,0 +1,4 @@
project.key=foo

# overridden property
sonar.host.url=http://overridden/sonar

+ 2
- 0
src/test/resources/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/runner/conf/sonar-runner.properties View File

@@ -0,0 +1,2 @@
sonar.host.url=http://localhost/sonar
sonar.jdbc.url=jdbc:mysql:localhost/sonar

+ 1
- 0
src/test/resources/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByDirectPath/other-conf.properties View File

@@ -0,0 +1 @@
sonar.host.url=http://other/sonar

+ 2
- 0
src/test/resources/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByHome/conf/sonar-runner.properties View File

@@ -0,0 +1,2 @@
sonar.host.url=http://moon/sonar
sonar.jdbc.url=jdbc:mysql:localhost/sonar

+ 1
- 0
src/test/resources/org/sonar/runner/RunnerTest/shouldInitDirs/fake.txt View File

@@ -0,0 +1 @@
fake

Loading…
Cancel
Save