From fa7edc55b2ecf3a3e02c674a41ca0b3e0194fe08 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 22 Dec 2020 09:26:20 +0100 Subject: [PATCH] Implemented serve task for development mode --- {buildSrc => build-plugins}/build.gradle | 12 +- .../cloudogu/scm/CopyCorePluginsTask.groovy | 0 .../scm/CorePluginsGradlePlugin.groovy | 0 .../groovy/com/cloudogu/scm/ScmServer.groovy | 140 ++++++++++++++++++ .../com/cloudogu/scm/ServePlugin.groovy | 49 ++++++ .../groovy/com/cloudogu/scm/ServeTask.groovy | 84 +++++++++++ .../cloudogu/scm/WriteServerConfigTask.groovy | 92 ++++++++++++ build.gradle | 4 + scm-ui/build.gradle | 6 +- scm-webapp/build.gradle | 13 ++ settings.gradle | 31 ++-- 11 files changed, 410 insertions(+), 21 deletions(-) rename {buildSrc => build-plugins}/build.gradle (79%) rename {buildSrc => build-plugins}/src/main/groovy/com/cloudogu/scm/CopyCorePluginsTask.groovy (100%) rename {buildSrc => build-plugins}/src/main/groovy/com/cloudogu/scm/CorePluginsGradlePlugin.groovy (100%) create mode 100644 build-plugins/src/main/groovy/com/cloudogu/scm/ScmServer.groovy create mode 100644 build-plugins/src/main/groovy/com/cloudogu/scm/ServePlugin.groovy create mode 100644 build-plugins/src/main/groovy/com/cloudogu/scm/ServeTask.groovy create mode 100644 build-plugins/src/main/groovy/com/cloudogu/scm/WriteServerConfigTask.groovy diff --git a/buildSrc/build.gradle b/build-plugins/build.gradle similarity index 79% rename from buildSrc/build.gradle rename to build-plugins/build.gradle index 0230259285..4072d76fc2 100644 --- a/buildSrc/build.gradle +++ b/build-plugins/build.gradle @@ -29,8 +29,11 @@ plugins { } dependencies { - implementation(gradleApi()) + implementation gradleApi() implementation 'com.google.guava:guava:30.0-jre' + implementation 'com.github.node-gradle:gradle-node-plugin:2.2.4' + implementation 'org.eclipse.jetty:jetty-server:9.4.35.v20201120' + implementation 'org.eclipse.jetty:jetty-webapp:9.4.35.v20201120' } gradlePlugin { @@ -40,6 +43,10 @@ gradlePlugin { id = 'org.scm-manager.core-plugins' implementationClass = 'com.cloudogu.scm.CorePluginsGradlePlugin' } + serve { + id = 'org.scm-manager.serve' + implementationClass = 'com.cloudogu.scm.ServePlugin' + } } } @@ -47,4 +54,7 @@ repositories { maven { url "https://packages.scm-manager.org/repository/public/" } + maven { + url 'https://plugins.gradle.org/m2/' + } } diff --git a/buildSrc/src/main/groovy/com/cloudogu/scm/CopyCorePluginsTask.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/CopyCorePluginsTask.groovy similarity index 100% rename from buildSrc/src/main/groovy/com/cloudogu/scm/CopyCorePluginsTask.groovy rename to build-plugins/src/main/groovy/com/cloudogu/scm/CopyCorePluginsTask.groovy diff --git a/buildSrc/src/main/groovy/com/cloudogu/scm/CorePluginsGradlePlugin.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/CorePluginsGradlePlugin.groovy similarity index 100% rename from buildSrc/src/main/groovy/com/cloudogu/scm/CorePluginsGradlePlugin.groovy rename to build-plugins/src/main/groovy/com/cloudogu/scm/CorePluginsGradlePlugin.groovy diff --git a/build-plugins/src/main/groovy/com/cloudogu/scm/ScmServer.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/ScmServer.groovy new file mode 100644 index 0000000000..d6c5c0b53e --- /dev/null +++ b/build-plugins/src/main/groovy/com/cloudogu/scm/ScmServer.groovy @@ -0,0 +1,140 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.cloudogu.scm + +import com.google.common.base.Strings +import groovy.json.JsonSlurper +import org.eclipse.jetty.server.HttpConfiguration +import org.eclipse.jetty.server.HttpConnectionFactory +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.util.component.AbstractLifeCycle +import org.eclipse.jetty.util.component.LifeCycle +import org.eclipse.jetty.webapp.WebAppContext + +import java.awt.Desktop + +public class ScmServer { + + def configuration + private Server server + + private ScmServer(def configuration) { + this.configuration = configuration + } + + void start() throws Exception { + info('start scm-server at port %s', configuration.port) + + System.setProperty('scm.home', configuration.home) + if (configuration.disableCorePlugins) { + info('disable core plugin extraction') + System.setProperty('sonia.scm.boot.disable-core-plugin-extraction', 'true') + } + + if (!Strings.isNullOrEmpty(configuration.loggingConfiguration)) { + System.setProperty('logback.configurationFile', configuration.loggingConfiguration); + } + + info('set stage %s', configuration.stage) + System.setProperty('scm.stage', configuration.stage) + + server = new Server() + server.addConnector(createServerConnector(server)) + server.setHandler(createScmContext()) + server.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() { + @Override + void lifeCycleStarted(LifeCycle event) { + + String endpoint = String.format('http://localhost:%d%s', configuration.port, configuration.contextPath) + + System.out.println() + System.out.println('==> scm-server started successfully and is accessible at:') + System.out.append('==> ').println(endpoint) + System.out.println() + + if (configuration.openBrowser) { + openBrowser(endpoint) + } + } + }) + + server.start() + } + + private static void openBrowser(String endpoint) { + try { + Desktop desktop = Desktop.getDesktop() + desktop.browse(URI.create(endpoint)) + } catch (IOException | URISyntaxException ex) { + warn('could not open browser', ex) + } + } + + private static void info(String message, Object... args) { + log('INFO', message, args) + } + + private static void warn(String message, Exception exception) { + log('WARN', message) + exception.printStackTrace(System.out) + } + + private static void log(String level, String template, Object... args) { + System.out.println("[${level}] " + String.format(template, args)) + } + + private WebAppContext createScmContext() { + WebAppContext warContext = new WebAppContext() + + warContext.setContextPath(configuration.contextPath) + warContext.setExtractWAR(true) + warContext.setWar(configuration.warFile) + + return warContext + } + + private ServerConnector createServerConnector(Server server) throws MalformedURLException { + ServerConnector connector = new ServerConnector(server) + HttpConfiguration cfg = new HttpConfiguration() + + cfg.setRequestHeaderSize(configuration.headerSize) + cfg.setResponseHeaderSize(configuration.headerSize) + + connector.setConnectionFactories([new HttpConnectionFactory(cfg)]) + connector.setPort(configuration.port) + + return connector + } + + static void main(String[] args) { + String configurationPath = args[0] + JsonSlurper slurper = new JsonSlurper() + def configuration = slurper.parse(new File(configurationPath)) + ScmServer server = new ScmServer(configuration) + server.start() + } + +} diff --git a/build-plugins/src/main/groovy/com/cloudogu/scm/ServePlugin.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/ServePlugin.groovy new file mode 100644 index 0000000000..96efa01d08 --- /dev/null +++ b/build-plugins/src/main/groovy/com/cloudogu/scm/ServePlugin.groovy @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.cloudogu.scm + +import org.gradle.api.Plugin +import org.gradle.api.Project +import com.moowork.gradle.node.NodeExtension + + +class ServePlugin implements Plugin { + + void apply(Project project) { + project.plugins.apply("com.github.node-gradle.node") + def nodeExt = NodeExtension.get(project) + nodeExt.setDownload(true) + nodeExt.setVersion('14.15.1') + nodeExt.setYarnVersion('1.22.5') + nodeExt.setNodeModulesDir( project.rootProject.projectDir ) + + project.tasks.register('write-server-config', WriteServerConfigTask) + project.tasks.register("serve", ServeTask) { + dependsOn 'write-server-config', 'yarnSetup' + } + } + +} diff --git a/build-plugins/src/main/groovy/com/cloudogu/scm/ServeTask.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/ServeTask.groovy new file mode 100644 index 0000000000..857e9bd2c6 --- /dev/null +++ b/build-plugins/src/main/groovy/com/cloudogu/scm/ServeTask.groovy @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.cloudogu.scm + +import com.moowork.gradle.node.yarn.YarnTask +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.TaskAction + +class ServeTask extends DefaultTask { + + @TaskAction + void exec() { + List> actions = new ArrayList<>(); + actions.add(createBackend()) + actions.add(createFrontend()) + + def threads = start(actions) + wait(threads) + } + + private static void wait(List threads) { + for (Thread thread : threads) { + thread.join() + } + } + + private static List start(List> actions) { + return actions.collect({ action -> + Thread thread = new Thread(action) + thread.start() + return thread + }) + } + + private Closure createBackend() { + def backend = project.tasks.create('boot-backend', JavaExec) { + main ScmServer.class.name + args(new File(project.buildDir, 'server/config.json').toString()) + environment 'NODE_ENV', 'development' + classpath project.buildscript.configurations.classpath + } + return { + backend.exec() + } + } + + private Closure createFrontend() { + def frontend = project.tasks.create('boot-frontend', YarnTask) { + args = ['run', 'serve'] + environment = [ + 'NODE_ENV': 'development' + ] + } + return { + frontend.exec() + } + } + +} diff --git a/build-plugins/src/main/groovy/com/cloudogu/scm/WriteServerConfigTask.groovy b/build-plugins/src/main/groovy/com/cloudogu/scm/WriteServerConfigTask.groovy new file mode 100644 index 0000000000..6b416473af --- /dev/null +++ b/build-plugins/src/main/groovy/com/cloudogu/scm/WriteServerConfigTask.groovy @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the 'Software'), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.cloudogu.scm + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal + +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import groovy.xml.MarkupBuilder +import java.io.BufferedWriter +import org.gradle.api.tasks.Input + +import com.google.common.hash.Hashing +import com.google.common.io.Files +import groovy.json.JsonOutput + +class WriteServerConfigTask extends DefaultTask { + + @Input + public String getHome() { + if (project.hasProperty('home')) { + return project.getProperty('home') + } + return new File(project.buildDir, 'scm-home').toString() + } + + @Input + public int getPort() { + if (project.hasProperty('port')) { + return Integer.parseInt(project.getProperty('port')) + } + return 8081 + } + + @InputFile + public File getWarFile() { + return new File(project.buildDir, 'libs/scm-webapp-dev.war') + } + + @OutputFile + public File getServerConfig() { + return new File(project.buildDir, 'server/config.json') + } + + @TaskAction + void execute() { + File serverConfig = getServerConfig() + serverConfig.getParentFile().mkdirs() + serverConfig.text = JsonOutput.toJson([ + home: getHome(), + port: getPort(), + contextPath: '/scm', + stage: 'DEVELOPMENT', + headerSize: 16384, + openBrowser: true, + warFile: getWarFile().toString() + ]) + } + +} diff --git a/build.gradle b/build.gradle index 2c63dd2e3a..00208d3e08 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,10 @@ plugins { id 'java-platform' } +task('serve') { + dependsOn ':scm-ui:yarn_install', ':scm-webapp:serve' +} + dependencies { constraints { // lombok diff --git a/scm-ui/build.gradle b/scm-ui/build.gradle index 9cb4606071..723859238a 100644 --- a/scm-ui/build.gradle +++ b/scm-ui/build.gradle @@ -23,14 +23,14 @@ */ plugins { - id 'com.github.node-gradle.node' version '2.2.4' + id 'com.github.node-gradle.node' version '2.2.4' } node { download = true version = '14.15.1' yarnVersion = '1.22.5' - nodeModulesDir = file('${project.rootProject.projectDir}') + nodeModulesDir = file(project.rootProject.projectDir) } // TODO define inputs and outputs @@ -50,7 +50,7 @@ task typecheck(type: YarnTask) { doLast { File directory = new File(project.buildDir, 'tmp/typecheck') directory.mkdirs() - File marker = new File(directory, "marker") + File marker = new File(directory, 'marker') marker.createNewFile() } } diff --git a/scm-webapp/build.gradle b/scm-webapp/build.gradle index f20ebfb36b..9a4bf960af 100644 --- a/scm-webapp/build.gradle +++ b/scm-webapp/build.gradle @@ -25,6 +25,7 @@ plugins { id 'war' id 'org.scm-manager.core-plugins' + id 'org.scm-manager.serve' id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.1.6' } @@ -144,6 +145,18 @@ war { from project.configurations.assets dependsOn 'copy-core-plugins' } + +// war without assets for development and livereload +task 'dev-war' (type: War) { + archiveName 'scm-webapp-dev.war' + from 'build/war' + dependsOn 'copy-core-plugins' +} + +serve { + dependsOn 'dev-war' +} + /** WTF??? diff --git a/settings.gradle b/settings.gradle index 03ca67ab82..333861d620 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,22 +24,19 @@ rootProject.name = 'scm' -def includePlugin(String name) { - include ":${name}" - project(":${name}").projectDir = new File("scm-plugins/${name}") -} +includeBuild 'build-plugins' +include 'scm-annotations' +include 'scm-annotation-processor' +include 'scm-core' +include 'scm-test' +include 'scm-ui' +include 'scm-plugins:scm-git-plugin' +include 'scm-plugins:scm-hg-plugin' +include 'scm-plugins:scm-svn-plugin' +include 'scm-plugins:scm-legacy-plugin' +include 'scm-plugins:scm-integration-test-plugin' +include 'scm-dao-xml' +include 'scm-webapp' +include 'scm-server' -include('scm-annotations') -include('scm-annotation-processor') -include('scm-core') -include('scm-test') -// include('scm-ui') -includePlugin('scm-git-plugin') -includePlugin('scm-hg-plugin') -includePlugin('scm-svn-plugin') -includePlugin('scm-legacy-plugin') -includePlugin('scm-integration-test-plugin') -include('scm-dao-xml') -include('scm-webapp') -include('scm-server') includeBuild '../gradle-smp-plugin'