2 * Copyright © 2019 IBM.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.onap.ccsdk.cds.controllerblueprints.core.scripts
19 import kotlinx.coroutines.async
20 import kotlinx.coroutines.coroutineScope
21 import org.jetbrains.kotlin.cli.common.ExitCode
22 import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
23 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
24 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
25 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
26 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
27 import org.jetbrains.kotlin.config.Services
28 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
29 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException
30 import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists
31 import org.onap.ccsdk.cds.controllerblueprints.core.logger
32 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintFileUtils
34 import java.net.URLClassLoader
35 import java.util.ArrayList
36 import kotlin.script.experimental.api.SourceCode
37 import kotlin.script.experimental.jvm.util.classpathFromClasspathProperty
38 import kotlin.system.measureTimeMillis
40 open class BluePrintCompileService {
41 val log = logger(BluePrintCompileService::class)
44 val classPaths = classpathFromClasspathProperty()?.joinToString(File.pathSeparator) {
49 /** Compile the [bluePrintSourceCode] and get the [kClassName] instance for the constructor [args] */
51 bluePrintSourceCode: BluePrintSourceCode,
53 args: ArrayList<Any?>?
55 /** Compile the source code if needed */
56 log.debug("Jar Exists : ${bluePrintSourceCode.targetJarFile.exists()}, Regenerate : ${bluePrintSourceCode.regenerate}")
57 if (!bluePrintSourceCode.targetJarFile.exists() || bluePrintSourceCode.regenerate) {
58 compile(bluePrintSourceCode)
61 val classLoaderWithDependencies = if (BluePrintConstants.USE_SCRIPT_COMPILE_CACHE) {
62 /** Get the class loader with compiled jar from cache */
63 BluePrintCompileCache.classLoader(bluePrintSourceCode.cacheKey)
65 /** Get the class loader with compiled jar from disk */
66 BluePrintFileUtils.getURLClassLoaderFromDirectory(bluePrintSourceCode.cacheKey)
69 /** Create the instance from the class loader */
70 return instance(classLoaderWithDependencies, kClassName, args)
73 /** Compile [bluePrintSourceCode] and put into cache */
74 suspend fun compile(bluePrintSourceCode: BluePrintSourceCode) {
75 // TODO("Include Multiple folders")
76 val sourcePath = bluePrintSourceCode.blueprintKotlinSources.first()
77 val compiledJarFile = bluePrintSourceCode.targetJarFile
79 log.info("compiling for cache key(${bluePrintSourceCode.cacheKey})")
81 val timeTaken = measureTimeMillis {
82 /** Create compile arguments */
83 val args = mutableListOf<String>().apply {
87 add(bluePrintSourceCode.moduleName)
92 add(compiledJarFile.absolutePath)
94 val deferredCompile = async {
95 val k2jvmCompiler = K2JVMCompiler()
96 /** Construct Arguments */
97 val arguments = k2jvmCompiler.createArguments()
98 parseCommandLineArguments(args, arguments)
99 val messageCollector = CompilationMessageCollector()
100 /** Compile with arguments */
101 val exitCode: ExitCode = k2jvmCompiler.exec(messageCollector, Services.EMPTY, arguments)
104 checkFileExists(compiledJarFile) { "couldn't generate compiled jar(${compiledJarFile.absolutePath})" }
107 throw BluePrintException("$exitCode :${messageCollector.errors().joinToString("\n")}")
111 deferredCompile.await()
113 log.info("compiled in ($timeTaken)mSec for cache key(${bluePrintSourceCode.cacheKey})")
117 /** create class [kClassName] instance from [classLoader] */
118 fun <T> instance(classLoader: URLClassLoader, kClassName: String, args: ArrayList<Any?>? = arrayListOf()): T {
119 val kClazz = classLoader.loadClass(kClassName)
120 ?: throw BluePrintException("failed to load class($kClassName) from current class loader.")
122 val instance = if (args.isNullOrEmpty()) {
126 .single().newInstance(*args.toArray())
127 } ?: throw BluePrintException("failed to create class($kClassName) instance for constructor argument($args).")
129 if (!BluePrintConstants.USE_SCRIPT_COMPILE_CACHE) {
136 /** Compile source code information */
137 open class BluePrintSourceCode : SourceCode {
139 lateinit var blueprintKotlinSources: MutableList<String>
140 lateinit var moduleName: String
141 lateinit var targetJarFile: File
142 lateinit var cacheKey: String
143 var regenerate: Boolean = false
145 override val text: String
148 override val locationId: String? = null
150 override val name: String?
154 /** Class to collect compilation Data */
155 data class CompiledMessageData(
156 val severity: CompilerMessageSeverity,
158 val location: CompilerMessageLocation?
161 /** Class to collect compilation results */
162 class CompilationMessageCollector : MessageCollector {
164 private val compiledMessages: MutableList<CompiledMessageData> = arrayListOf()
166 override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
167 synchronized(compiledMessages) {
168 compiledMessages.add(CompiledMessageData(severity, message, location))
172 override fun hasErrors() =
173 synchronized(compiledMessages) {
174 compiledMessages.any { it.severity.isError }
177 override fun clear() {
178 synchronized(compiledMessages) {
179 compiledMessages.clear()
183 fun errors(): List<CompiledMessageData> = compiledMessages.filter { it.severity == CompilerMessageSeverity.ERROR }