rjaros / kvision

Object oriented web framework for Kotlin/JS
https://kvision.io
MIT License
1.23k stars 65 forks source link

Google App Engine #118

Closed jakubMitura14 closed 1 month ago

jakubMitura14 commented 4 years ago

Hello I am trying to run full stack template on GAE but without success I would appriciate help what needs to be done

error

`* What went wrong:
Project 'google-appengine-standard' not found in root project 'template-fullstack-ktor'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan
 to get full insights.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/5.5.1/userguide/command_line_interface.html#sec:command_line_warnings
`

build gradle

`import org.jetbrains.kotlin.gradle.frontend.KotlinFrontendExtension
import org.jetbrains.kotlin.gradle.frontend.npm.NpmExtension
import org.jetbrains.kotlin.gradle.frontend.webpack.WebPackExtension
import org.jetbrains.kotlin.gradle.frontend.webpack.WebPackRunTask
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinJsDce
import org.jetbrains.kotlin.ir.backend.js.compile

buildscript {
    extra.set("production", (findProperty("prod") ?: findProperty("production") ?: "false") == "true")

    dependencies {
        classpath("pl.treksoft:kvision-gradle-plugin:${System.getProperty("kvisionVersion")}")
        classpath("com.google.cloud.tools:appengine-gradle-plugin:1.+")
        classpath ("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
       // classpath ("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
    }
}

plugins {
    val kotlinVersion: String by System.getProperties()
    id("kotlinx-serialization") version kotlinVersion
    id("kotlin-multiplatform") version kotlinVersion
    id("kotlin-dce-js") version kotlinVersion
    kotlin("frontend") version System.getProperty("frontendPluginVersion")
    war
    //appengine
    id("com.google.cloud.tools.appengine") version System.getProperty("appenginePlugin")
}

apply(plugin = "pl.treksoft.kvision")

version = "1.0.0-SNAPSHOT"
group = "com.example"

repositories {
    mavenCentral()
    jcenter()
    maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
    maven { url = uri("https://kotlin.bintray.com/kotlinx") }
    maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
    maven { url = uri("https://dl.bintray.com/gbaldeck/kotlin") }
    maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
    maven {        url = uri("https://maven-central.storage.googleapis.com")             // Google's mirror of Maven Central
    }

    mavenLocal()
}

// Versions
val kotlinVersion: String by System.getProperties()
val ktorVersion: String by project
val exposedVersion: String by project
val hikariVersion: String by project
val h2Version: String by project
val pgsqlVersion: String by project
val kweryVersion: String by project
val logbackVersion: String by project
val kvisionVersion: String by System.getProperties()
val commonsCodecVersion: String by project
val jdbcNamedParametersVersion: String by project

// Custom Properties
val webDir = file("src/frontendMain/web")
val isProductionBuild = project.extra.get("production") as Boolean
val mainClassName = "io.ktor.server.netty.EngineMain"

kotlin {
    jvm("backend") {
        compilations.all {
            kotlinOptions {
                jvmTarget = "1.8"
            }
        }
    }
    js("frontend") {
        compilations.all {
            kotlinOptions {
                moduleKind = "umd"
                sourceMap = !isProductionBuild
                metaInfo = true
                if (!isProductionBuild) {
                    sourceMapEmbedSources = "always"
                }
            }
        }
    }

    sourceSets {
        getByName("commonMain") {
            dependencies {
                implementation(kotlin("stdlib-common"))
                implementation("pl.treksoft:kvision-common-types:$kvisionVersion")
                implementation("pl.treksoft:kvision-common-remote:$kvisionVersion")
                implementation("pl.treksoft:kvision-common-annotations:$kvisionVersion")
                implementation ("com.google.appengine:appengine-api-1.0-sdk:+")
                implementation( "javax.servlet:javax.servlet-api:3.1.0")
               // implementation ("com.google.appengine:appengine-java-sdk:1.9.3")
               // implementation( "com.google.appengine.archetypes:appengine-standard-archetype:1.0.2")
                //implementation("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
            //    api("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
            }
            kotlin.srcDir("build/generated-src/common")
        }
        getByName("commonTest") {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        getByName("backendMain") {
            dependencies {
                implementation(kotlin("stdlib-jdk8"))
                implementation(kotlin("reflect"))
                implementation("pl.treksoft:kvision-server-ktor:$kvisionVersion")
                implementation("io.ktor:ktor-server-netty:$ktorVersion")
                implementation("io.ktor:ktor-auth:$ktorVersion")
                implementation("ch.qos.logback:logback-classic:$logbackVersion")
                implementation("com.h2database:h2:$h2Version")
                implementation("org.jetbrains.exposed:exposed:$exposedVersion")
                implementation("org.postgresql:postgresql:$pgsqlVersion")
                implementation("com.zaxxer:HikariCP:$hikariVersion")
                implementation("commons-codec:commons-codec:$commonsCodecVersion")
                implementation("com.axiomalaska:jdbc-named-parameters:$jdbcNamedParametersVersion")
                implementation("com.github.andrewoma.kwery:core:$kweryVersion")
                implementation ("com.google.appengine:appengine-api-1.0-sdk:+")
             //   api("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
            }
        }
        getByName("backendTest") {
            dependencies {
                implementation(kotlin("test"))
                implementation(kotlin("test-junit"))
            }
        }
        getByName("frontendMain") {
            resources.srcDir(webDir)
            dependencies {
                implementation(kotlin("stdlib-js"))
                implementation("pl.treksoft:kvision:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-css:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-select:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-datetime:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-spinner:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-upload:$kvisionVersion")
//                implementation("pl.treksoft:kvision-bootstrap-dialog:$kvisionVersion")
//                implementation("pl.treksoft:kvision-fontawesome:$kvisionVersion")
                implementation("pl.treksoft:kvision-i18n:$kvisionVersion")
                implementation("pl.treksoft:kvision-richtext:$kvisionVersion")
                implementation("pl.treksoft:kvision-handlebars:$kvisionVersion")
                implementation("pl.treksoft:kvision-datacontainer:$kvisionVersion")
                implementation("pl.treksoft:kvision-redux:$kvisionVersion")
                implementation("pl.treksoft:kvision-chart:$kvisionVersion")
                implementation("pl.treksoft:kvision-tabulator:$kvisionVersion")
                implementation("pl.treksoft:kvision-pace:$kvisionVersion")
                implementation("pl.treksoft:kvision-moment:$kvisionVersion")
                implementation("pl.treksoft:kvision-remote:$kvisionVersion")
                implementation ("com.google.appengine:appengine-api-1.0-sdk:+")
             //   api("com.google.appengine:appengine:${System.getProperty("appengineVersion")}")
            }
            kotlin.srcDir("build/generated-src/frontend")
        }
        getByName("frontendTest") {
            dependencies {
                implementation(kotlin("test-js"))
            }
        }
    }
}

ktor {
    port = 8080
    mainClass = mainClassName
    jvmOptions = arrayOf()
    workDir = buildDir
}

kotlinFrontend {
    sourceMaps = !isProductionBuild
    npm {
        devDependency("po2json")
        devDependency("grunt")
        devDependency("grunt-pot")
    }
    webpackBundle {
        bundleName = "main"
        sourceMapEnabled = false
        port = 3000
        proxyUrl = "http://localhost:${ktor.port}"
        contentPath = webDir
        mode = if (isProductionBuild) "production" else "development"
    }

    define("PRODUCTION", isProductionBuild)
}

tasks {
    withType<Kotlin2JsCompile> {
        kotlinOptions {
            moduleKind = "umd"
            sourceMap = !isProductionBuild
            metaInfo = true
            if (!isProductionBuild) {
                sourceMapEmbedSources = "always"
            }
        }
    }
    withType<KotlinJsDce> {
        dceOptions {
            devMode = !isProductionBuild
        }
        inputs.property("production", isProductionBuild)
        doFirst {
            destinationDir.deleteRecursively()
        }
        doLast {
            copy {
                file("$buildDir/node_modules_imported/").listFiles()?.forEach {
                    if (it.isDirectory && it.name.startsWith("kvision")) {
                        from(it) {
                            include("css/**")
                            include("img/**")
                            include("js/**")
                        }
                    }
                }
                into(file(buildDir.path + "/kotlin-js-min/frontend/main"))
            }
        }
    }
    create("generateGruntfile") {
        outputs.file("$buildDir/Gruntfile.js")
        doLast {
            file("$buildDir/Gruntfile.js").run {
                writeText(
                        """
                   module.exports = function (grunt) {
                       grunt.initConfig({
                           pot: {
                               options: {
                                   text_domain: "messages",
                                   dest: "../src/frontendMain/resources/i18n/",
                                   keywords: ["tr", "ntr:1,2", "gettext", "ngettext:1,2"],
                                   encoding: "UTF-8"
                               },
                               files: {
                                   src: ["../src/frontendMain/kotlin/**/*.kt"],
                                   expand: true,
                               },
                           }
                       });
                       grunt.loadNpmTasks("grunt-pot");
                   };
               """.trimIndent()
                )
            }
        }
    }
    create("generatePotFile", Exec::class) {
        dependsOn("npm-install", "generateGruntfile")
        workingDir = file("$buildDir")
        executable = NodeJsRootPlugin.apply(project).nodeCommand
        args("$buildDir/node_modules/grunt/bin/grunt", "pot")
        inputs.files(kotlin.sourceSets["frontendMain"].kotlin.files)
        outputs.file("$projectDir/src/frontendMain/resources/i18n/messages.pot")
    }
}
afterEvaluate {
    tasks {
        getByName("frontendProcessResources", Copy::class) {
            dependsOn("npm-install")
            exclude("**/*.pot")
            doLast("Convert PO to JSON") {
                destinationDir.walkTopDown().filter {
                    it.isFile && it.extension == "po"
                }.forEach {
                    exec {
                        executable = NodeJsRootPlugin.apply(project).nodeCommand
                        args(
                                "$buildDir/node_modules/po2json/bin/po2json",
                                it.absolutePath,
                                "${it.parent}/${it.nameWithoutExtension}.json",
                                "-f",
                                "jed1.x"
                        )
                        println("Converted ${it.name} to ${it.nameWithoutExtension}.json")
                    }
                    it.delete()
                }
            }
        }
        getByName("webpack-run", WebPackRunTask::class) {
            dependsOn("frontendMainClasses")
            doFirst {
                copy {
                    from((project.tasks["frontendProcessResources"] as Copy).destinationDir)
                    into((project.tasks["processResources"] as Copy).destinationDir)
                }
            }
        }
        getByName("webpack-bundle") {
            dependsOn("frontendMainClasses", "runDceFrontendKotlin")
            doFirst {
                copy {
                    from((project.tasks["frontendProcessResources"] as Copy).destinationDir)
                    into((project.tasks["processResources"] as Copy).destinationDir)
                }
            }
        }
        replace("frontendJar", Jar::class).apply {
            dependsOn("webpack-bundle")
            group = "package"
            archiveAppendix.set("frontend")
            val from = project.tasks["webpack-bundle"].outputs.files + webDir
            from(from)
            into("/assets")
            inputs.files(from)
            outputs.file(archiveFile)

            manifest {
                attributes(
                        mapOf(
                                "Implementation-Title" to rootProject.name,
                                "Implementation-Group" to rootProject.group,
                                "Implementation-Version" to rootProject.version,
                                "Timestamp" to System.currentTimeMillis()
                        )
                )
            }
        }
        create("frontendZip", Zip::class) {
            dependsOn("webpack-bundle")
            group = "package"
            archiveAppendix.set("frontend")
            destinationDirectory.set(file("$buildDir/libs"))
            val from = project.tasks["webpack-bundle"].outputs.files + webDir
            from(from)
            inputs.files(from)
            outputs.file(archiveFile)
        }
        getByName("backendJar").group = "package"
        replace("jar", Jar::class).apply {
            dependsOn("frontendJar", "backendJar")
            group = "package"
            manifest {
                attributes(
                        mapOf(
                                "Implementation-Title" to rootProject.name,
                                "Implementation-Group" to rootProject.group,
                                "Implementation-Version" to rootProject.version,
                                "Timestamp" to System.currentTimeMillis(),
                                "Main-Class" to mainClassName
                        )
                )
            }
            val dependencies = configurations["backendRuntimeClasspath"].filter { it.name.endsWith(".jar") } +
                    project.tasks["backendJar"].outputs.files +
                    project.tasks["frontendJar"].outputs.files
            dependencies.forEach {
                if (it.isDirectory) from(it) else from(zipTree(it))
            }
            exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
            inputs.files(dependencies)
            outputs.file(archiveFile)
        }
        create("frontendRun") {
            dependsOn("webpack-run")
            group = "run"
        }
        create("backendRun") {
            dependsOn("ktor-run")
            group = "run"
        }
        getByName("run") {
            dependsOn("frontendRun", "backendRun","appengineRun")
        }
        create("frontendStop") {
            dependsOn("webpack-stop")
            group = "run"
        }
        create("backendStop") {
            dependsOn("ktor-stop")
            group = "run"
        }
        getByName("stop") {
            dependsOn("frontendStop", "backendStop")
        }
        getByName("compileKotlinBackend") {
            dependsOn("compileKotlinMetadata")
        }
        getByName("compileKotlinFrontend") {
            dependsOn("compileKotlinMetadata")
        }
    }
}

fun KotlinFrontendExtension.webpackBundle(block: WebPackExtension.() -> Unit) =
        bundle("webpack", delegateClosureOf(block))

fun KotlinFrontendExtension.npm(block: NpmExtension.() -> Unit) = configure(block)
`

settings gradle

`pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
        google()
        jcenter()
        maven { url = uri("https://plugins.gradle.org/m2/") }
        maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
        maven { url = uri("https://kotlin.bintray.com/kotlinx") }
        maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
        mavenLocal()
    }
    resolutionStrategy {
        eachPlugin {
            when {
                requested.id.id == "kotlin-multiplatform" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
                requested.id.id == "org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:${requested.version}")
                requested.id.id == "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
                requested.id.id == "kotlin-dce-js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
                requested.id.id == "com.google.cloud.tools.appengine"-> useModule("com.google.cloud.tools:appengine-gradle-plugin:${requested.version}")
            }
        }
    }
}
rootProject.name = "template-fullstack-ktor"

`

gradle properties

`javaVersion=1.8
kotlin.incremental.js=false
#Plugins
systemProp.kotlinVersion=1.3.61
systemProp.frontendPluginVersion=0.0.45
serializationVersion=0.14.0
systemProp.appenginePlugin = 2.2.0
#Dependencies
systemProp.kvisionVersion=2.5.0
ktorVersion=1.2.6
hikariVersion=3.2.0
commonsCodecVersion=1.10
jdbcNamedParametersVersion=1.1
exposedVersion=0.12.1
pac4jVersion=2.3.1
logbackVersion=1.2.3
h2Version=1.4.197
pgsqlVersion=42.2.2
kweryVersion=0.17
systemProp.appengineVersion = 1.9.77
`
rjaros commented 4 years ago

As far as I understand how GAE works, the application needs to work as JEE servlet. So you have to prepare Ktor application for the servlet engine. I would start with plain Ktor project using the following references: https://ktor.io/servers/deploy/hosting/google-app-engine.html https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard https://cloud.google.com/community/tutorials/kotlin-ktor-app-engine-java8

When you will have it working, try to add KVision. But unfortunately I'm not even sure KVision can work with Ktor running on servlet engine. Also remember that KVision build process needs npm environment. I'm not sure how to make it available with GAE (on Heroku it's done by adding both nodejs and gradle buildpacks at the same time).

Please let me know if you succeed with GAE :-)

jagiellonczyk14 commented 4 years ago

To specify the Google app engine was installed on system and the template before I tried to mess with GAE worked well, ok If I will manage it I will publish the results, in the meantime maybe I am blind but Is there heroku walkthrough?

rjaros commented 4 years ago

There is no guide, but at least it's tested :-) The example applications include ready to use Procfile https://github.com/rjaros/kvision-examples/blob/master/encoder-fullstack-ktor/Procfile

More information can be found here: https://github.com/rjaros/kvision/issues/48