diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml new file mode 100644 index 0000000..bfb6398 --- /dev/null +++ b/.github/workflows/push-master.yml @@ -0,0 +1,32 @@ +# A push on master will build, test and publish Jeka on OSSRH. +# Beside it will push the built documentation on the Jeka landing page. + +# If a tag is present on the last commit, Jeka will publish versioned artifacts according tag name on OSSRH public repo. +# If no tag is present, the built artifacts will be published to OSSRH snapshot repository. +# This logic is implemented within the Jeka build. + +name: Java Build and Publish + +on: + push: + branches: [ master ] + workflow_dispatch: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Jekaw + env: + OSSRH_USER: ${{ secrets.OSSRH_USER }} + OSSRH_PWD: ${{ secrets.OSSRH_PWD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + cd ./dev.jeka.plugins.sonarqube + ./jekaw java#pack java#publish diff --git a/README.md b/README.md index 76f5aff..e2d4efb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ +[![Maven Central](https://img.shields.io/maven-central/v/dev.jeka/sonarqube-plugin.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22dev.jeka22%20AND%20a:%22springboot-plugin%22) + # sonarqube-plugin A Sonarqube plugin for Jeka diff --git a/breaking_versions.txt b/breaking_versions.txt new file mode 100644 index 0000000..000e250 --- /dev/null +++ b/breaking_versions.txt @@ -0,0 +1,3 @@ +## Next line means plugin 2.4.0.RC11 is not compatile with Jeka 0.9.0.RELEASE and above +## 2.4.0.RC11 : 0.9.0.RELEASE + diff --git a/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkPluginSonarqube.java b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkPluginSonarqube.java new file mode 100644 index 0000000..b18bf56 --- /dev/null +++ b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkPluginSonarqube.java @@ -0,0 +1,83 @@ +package dev.jeka.plugins.sonarqube; + +import dev.jeka.core.api.depmanagement.JkDependencySet; +import dev.jeka.core.api.depmanagement.JkModuleId; +import dev.jeka.core.api.file.JkPathSequence; +import dev.jeka.core.api.java.project.JkCompileLayout; +import dev.jeka.core.api.java.project.JkJavaProject; +import dev.jeka.core.api.java.project.JkJavaProjectConstruction; +import dev.jeka.core.tool.*; +import dev.jeka.core.tool.builtins.java.JkPluginJava; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +@JkDoc("Run SonarQube analysis.") +@JkDocPluginDeps(JkPluginJava.class) +public class JkPluginSonarqube extends JkPlugin { + + private final Map properties = new HashMap<>(); + + public JkPluginSonarqube(JkClass jkClass) { + super(jkClass); + } + + public static JkSonarqube configureSonarFrom(JkJavaProject project) { + final JkCompileLayout prodLayout = project.getConstruction().getCompilation().getLayout(); + final JkCompileLayout testLayout = project.getConstruction().getTesting().getCompilation().getLayout(); + final Path baseDir = project.getBaseDir(); + JkJavaProjectConstruction construction = project.getConstruction(); + JkDependencySet deps = construction.getCompilation().getDependencies() + .merge(construction.getRuntimeDependencies()).getResult(); + final JkPathSequence libs = project.getConstruction().getDependencyResolver().resolve(deps).getFiles(); + final Path testReportDir = project.getConstruction().getTesting().getReportDir(); + final JkModuleId moduleId = project.getPublication().getModuleId(); + final String version = project.getPublication().getVersion(); + final String fullName = moduleId.getDotedName(); + final String name = moduleId.getName(); + return JkSonarqube + .of(fullName, name, version) + .withProperties(JkOptions.getAllStartingWith("sonar.")).withProjectBaseDir(baseDir) + .withBinaries(project.getConstruction().getCompilation().getLayout().resolveClassDir()) + .withLibraries(libs) + .withSourcesPath(prodLayout.resolveSources().getRootDirsOrZipFiles()) + .withTestPath(testLayout.resolveSources().getRootDirsOrZipFiles()) + .withProperty(JkSonarqube.WORKING_DIRECTORY, baseDir.resolve(JkConstants.JEKA_DIR + "/.sonar").toString()) + .withProperty(JkSonarqube.JUNIT_REPORTS_PATH, + baseDir.relativize( testReportDir.resolve("junit")).toString()) + .withProperty(JkSonarqube.SUREFIRE_REPORTS_PATH, + baseDir.relativize(testReportDir.resolve("junit")).toString()) + .withProperty(JkSonarqube.SOURCE_ENCODING, project.getConstruction().getSourceEncoding()) + .withProperty(JkSonarqube.JACOCO_REPORTS_PATHS, + baseDir.relativize(project.getOutputDir().resolve("jacoco/jacoco.exec")).toString()); + + } + + @JkDoc("Runs sonar-scanner based on properties defined in this plugin. " + + "Options prefixed with 'sonar.' as '-sonar.host.url=http://myserver/..' " + + "will be appended to sonarQube properties.") + public void run() { + configureSonarFrom(getJkClass().getPlugins().get(JkPluginJava.class).getProject()) + .withProperties(properties) + .launchRunner(); + } + + @JkDoc("Runs sonar-runner based on properties defined in this plugin. " + + "Options prefixed with 'sonar.' as '-sonar.host.url=http://myserver/..' " + + "will be appended to sonarQube properties.") + public void runLegacy() { + configureSonarFrom(getJkClass().getPlugins().get(JkPluginJava.class).getProject()) + .withProperties(properties) + .launchRunner(); + } + + /** + * Adds a property to setupAfterPluginActivations sonar instance to run. You'll find predefined keys in {@link JkSonarqube}. + */ + public JkPluginSonarqube setProp(String key, String value) { + this.properties.put(key, value); + return this; + } + +} diff --git a/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkSonarqube.java b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkSonarqube.java new file mode 100644 index 0000000..099f1a8 --- /dev/null +++ b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/JkSonarqube.java @@ -0,0 +1,225 @@ +package dev.jeka.plugins.sonarqube; + + +import dev.jeka.core.api.file.JkPathFile; +import dev.jeka.core.api.java.JkInternalClassloader; +import dev.jeka.core.api.java.JkJavaProcess; +import dev.jeka.core.api.system.JkLog; +import dev.jeka.core.api.utils.JkUtilsAssert; +import dev.jeka.core.api.utils.JkUtilsIO; +import dev.jeka.core.api.utils.JkUtilsPath; +import dev.jeka.core.tool.JkConstants; + +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +/** + * Sonar wrapper class for launching sonar analysis in a convenient way. This + * Sonar wrapper is not specific to Java project so can be used for to analyse + * any kind of project supported by SonarQube. + * + * @author Jerome Angibaud + */ +public final class JkSonarqube { + + public static final String PROJECT_KEY = "projectKey"; + public static final String PROJECT_NAME = "projectName"; + public static final String PROJECT_VERSION = "projectVersion"; + public static final String LANGUAGE = "language"; + public static final String PROFILE = "profile"; + public static final String BRANCH = "branch"; + public static final String SOURCE_ENCODING = "sourceEncoding"; + public static final String VERBOSE = "verbose"; + public static final String WORKING_DIRECTORY = "working.directory"; + public static final String JUNIT_REPORTS_PATH = "junit.reportsPath"; + public static final String SUREFIRE_REPORTS_PATH = "surefire.reportsPath"; + public static final String JACOCO_REPORTS_PATHS = "jacoco.reportPaths"; + public static final String COVERTURA_REPORTS_PATH = "cobertura.reportPath"; + public static final String CLOVER_REPORTS_PATH = "clover.reportPath"; + public static final String DYNAMIC_ANALYSIS = "dynamicAnalysis"; + public static final String PROJECT_BASE_DIR = "projectBaseDir"; + public static final String SOURCES = "sources"; + public static final String BINARIES = "binaries"; + public static final String JAVA_BINARIES = "java.binaries"; + public static final String TEST = "tests"; + public static final String LIBRARIES = "libraries"; + public static final String SKIP_DESIGN = "skipDesign"; + public static final String HOST_URL = "host.url"; + public static final String JDBC_URL = "jdbc.url"; + public static final String JDBC_USERNAME = "jdbc.username"; + public static final String JDBC_PASSWORD = "jdbc.password"; + private static final String RUNNER_JAR_NAME_24 = "sonar-runner-2.4.jar"; + private static final String SCANNER_JAR_NAME_46 = "sonar-scanner-cli-4.6.2.2472.jar"; + private static final String RUNNER_LOCAL_PATH = JkConstants.OUTPUT_PATH + "/temp/" + RUNNER_JAR_NAME_24; + private static final String SONAR_PREFIX = "sonar."; + private final Map params; + + private final boolean enabled; + + private JkSonarqube(Map params, boolean enabled) { + super(); + this.params = Collections.unmodifiableMap(params); + this.enabled = enabled; + } + + public static JkSonarqube of(String projectKey, String projectName, String version) { + JkUtilsAssert.argument(projectName != null, "Project name can't be null."); + JkUtilsAssert.argument(projectKey != null, "Project key can't be null."); + JkUtilsAssert.argument(version != null, "Project version can't be null."); + final Map map = new HashMap<>(); + map.put(PROJECT_KEY, projectKey); + map.put(PROJECT_NAME, projectName); + map.put(PROJECT_VERSION, version); + map.put(WORKING_DIRECTORY, ".sonarTempDir"); + map.put(VERBOSE, Boolean.toString(JkLog.Verbosity.VERBOSE == JkLog.verbosity())); + final Properties properties = System.getProperties(); + for (final Object keyObject : properties.keySet()) { + final String key = (String) keyObject; + if (key.startsWith(SONAR_PREFIX)) { + map.put(key.substring(SONAR_PREFIX.length()), properties.getProperty(key)); + } + } + return new JkSonarqube(map, true); + } + + public void launchRunner() { + if (!enabled) { + JkLog.info("Sonar analysis skipped."); + } + JkLog.startTask("Launch Sonar analysis wih sonar-runner."); + if (JkLog.verbosity() == JkLog.Verbosity.VERBOSE) { + javaRunnerProcess().runClassSync("org.sonar.runner.Main", "-e", "-X"); + } else { + javaRunnerProcess().runClassSync("org.sonar.runner.Main", "-e"); + } + JkLog.endTask(); + } + + public void launchScanner() { + if (!enabled) { + JkLog.info("Sonar analysis skipped."); + } + JkLog.startTask("Launch Sonar analysis with sonar-scanner"); + if (JkLog.verbosity() == JkLog.Verbosity.VERBOSE) { + javaScannerProcess().runClassSync("org.sonar.runner.Main", "-e", "-X"); + } else { + javaScannerProcess().runClassSync("org.sonar.runner.Main", "-e"); + } + JkLog.endTask(); + } + + public JkSonarqube enabled(boolean enabled) { + return new JkSonarqube(this.params, enabled); + } + + // Runner is the legacy cliennt + private JkJavaProcess javaRunnerProcess() { + URL embeddedUrl = JkSonarqube.class.getResource(RUNNER_JAR_NAME_24); + Path cachedUrl = JkUtilsIO.copyUrlContentToCacheFile(embeddedUrl, null, JkInternalClassloader.URL_CACHE_DIR); + return JkJavaProcess.of().withClasspath(cachedUrl).andOptions(toProperties()); + } + + // Scanner replaces legacy Runner client + private JkJavaProcess javaScannerProcess() { + URL embeddedUrl = JkSonarqube.class.getResource(SCANNER_JAR_NAME_46); + Path cachedUrl = JkUtilsIO.copyUrlContentToCacheFile(embeddedUrl, null, JkInternalClassloader.URL_CACHE_DIR); + return JkJavaProcess.of().withClasspath(cachedUrl).andOptions(toProperties()); + } + + private List toProperties() { + final List result = new LinkedList<>(); + for (final Map.Entry entry : this.params.entrySet()) { + result.add("-Dsonar." + entry.getKey() + "=" + entry.getValue()); + } + return result; + } + + public JkSonarqube withProperty(String key, String value) { + return new JkSonarqube(andParams(key, value), enabled); + } + + public JkSonarqube withProperties(Map props) { + final Map newProps = new HashMap<>(this.params); + newProps.putAll(props); + return new JkSonarqube(newProps, enabled); + } + + public JkSonarqube withProjectBaseDir(Path baseDir) { + return withProperty(PROJECT_BASE_DIR, baseDir.toAbsolutePath().toString()); + } + + public JkSonarqube withSourcesPath(Iterable files) { + return withProperty(SOURCES, toPaths(JkUtilsPath.disambiguate(files))); + } + + + public JkSonarqube withTestPath(Iterable files) { + return withProperty(TEST, toPaths(files)); + } + + public JkSonarqube withBinaries(Iterable files) { + String path = toPaths(JkUtilsPath.disambiguate(files)); + return withProperty(BINARIES, path).withProperty(JAVA_BINARIES, path); + } + + public JkSonarqube withBinaries(Path... files) { + return withBinaries(Arrays.asList(files)); + } + + public JkSonarqube withLibraries(Iterable files) { + return withProperty(LIBRARIES, toPaths(JkUtilsPath.disambiguate(files))); + } + + public JkSonarqube withSkipDesign(boolean skip) { + return withProperty(SKIP_DESIGN, Boolean.toString(skip)); + } + + public JkSonarqube withHostUrl(String url) { + return withProperty(HOST_URL, url); + } + + public JkSonarqube withJdbcUrl(String url) { + return withProperty(JDBC_URL, url); + } + + public JkSonarqube withJdbcUserName(String userName) { + return withProperty(JDBC_USERNAME, userName); + } + + public JkSonarqube withJdbcPassword(String pwd) { + return withProperty(JDBC_PASSWORD, pwd); + } + + private String toPaths(Iterable files) { + final Iterator it = files.iterator(); + final StringBuilder result = new StringBuilder(); + final Path projectDir = projectDir(); + while (it.hasNext()) { + final Path file = it.next(); + String path; + if (file.startsWith(projectDir)) { + path = projectDir.relativize(file).toString(); + } else { + path = file.toAbsolutePath().toString(); + } + result.append(path); + if (it.hasNext()) { + result.append(","); + } + } + return result.toString(); + } + + private Map andParams(String key, String value) { + final Map newMap = new HashMap<>(this.params); + newMap.put(key, value); + return newMap; + } + + private Path projectDir() { + return Paths.get(this.params.get(PROJECT_BASE_DIR)); + } + +} diff --git a/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-runner-2.4.jar b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-runner-2.4.jar new file mode 100644 index 0000000..347b514 Binary files /dev/null and b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-runner-2.4.jar differ diff --git a/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-scanner-cli-4.6.2.2472.jar b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-scanner-cli-4.6.2.2472.jar new file mode 100644 index 0000000..3a9ce7f Binary files /dev/null and b/dev.jeka.plugins.sonarqube/src/main/java/dev/jeka/plugins/sonarqube/sonar-scanner-cli-4.6.2.2472.jar differ