blob: a752d25ec3cbd2dfb9d19e1d91ff12b07a185216 [file]
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.execution.build
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
import com.intellij.compiler.options.CompileStepBeforeRun
import com.intellij.execution.CantRunException
import com.intellij.execution.ExecutionBundle
import com.intellij.execution.Executor
import com.intellij.execution.ShortenCommandLine
import com.intellij.execution.application.ApplicationConfiguration
import com.intellij.execution.configurations.JavaParameters
import com.intellij.execution.configurations.JavaRunConfigurationModule
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.impl.RunManagerImpl
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.util.ExecutionErrorDialog
import com.intellij.execution.util.JavaParametersUtil
import com.intellij.execution.util.ProgramParametersUtil
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.ex.JavaSdkUtil
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiJavaModule
import com.intellij.task.ExecuteRunConfigurationTask
import com.intellij.util.PathUtil
import gnu.trove.THashMap
import org.jetbrains.plugins.gradle.execution.GradleRunnerUtil
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil
import org.jetbrains.plugins.gradle.service.task.GradleTaskManager
import org.jetbrains.plugins.gradle.util.GradleConstants
/**
* @author Vladislav.Soroka
*/
class GradleApplicationEnvironmentProvider : GradleExecutionEnvironmentProvider {
override fun isApplicable(task: ExecuteRunConfigurationTask): Boolean = task.runProfile is ApplicationConfiguration
override fun createExecutionEnvironment(project: Project,
executeRunConfigurationTask: ExecuteRunConfigurationTask,
executor: Executor?): ExecutionEnvironment? {
if (!isApplicable(executeRunConfigurationTask)) return null
val applicationConfiguration = executeRunConfigurationTask.runProfile as ApplicationConfiguration
val mainClass = applicationConfiguration.mainClass ?: return null
val virtualFile = mainClass.containingFile.virtualFile
val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(virtualFile) ?: return null
val params = JavaParameters().apply {
JavaParametersUtil.configureConfiguration(this, applicationConfiguration)
this.vmParametersList.addParametersString(applicationConfiguration.vmParameters)
}
val javaModuleName: String?
val javaExePath: String
try {
val jdk = JavaParametersUtil.createProjectJdk(project, applicationConfiguration.alternativeJrePath)
?: throw RuntimeException(ExecutionBundle.message("run.configuration.error.no.jdk.specified"))
val type = jdk.sdkType
if (type !is JavaSdkType) throw RuntimeException(ExecutionBundle.message("run.configuration.error.no.jdk.specified"))
javaExePath = (type as JavaSdkType).getVMExecutablePath(jdk)?.let {
FileUtil.toSystemIndependentName(it)
} ?: throw RuntimeException(ExecutionBundle.message("run.configuration.cannot.find.vm.executable"))
javaModuleName = findJavaModuleName(jdk, applicationConfiguration.configurationModule, mainClass)
}
catch (e: CantRunException) {
ExecutionErrorDialog.show(e, "Cannot use specified JRE", project)
throw RuntimeException(ExecutionBundle.message("run.configuration.cannot.find.vm.executable"))
}
val taskSettings = ExternalSystemTaskExecutionSettings()
taskSettings.isPassParentEnvs = params.isPassParentEnvs
taskSettings.env = if (params.env.isEmpty()) emptyMap() else THashMap(params.env)
taskSettings.externalSystemIdString = GradleConstants.SYSTEM_ID.id
val projectPath = GradleRunnerUtil.resolveProjectPath(module)
taskSettings.externalProjectPath = projectPath
val runAppTaskName = mainClass.name!! + ".main()"
taskSettings.taskNames = listOf(runAppTaskName)
val executorId = executor?.id ?: DefaultRunExecutor.EXECUTOR_ID
val environment = ExternalSystemUtil.createExecutionEnvironment(project, GradleConstants.SYSTEM_ID, taskSettings, executorId)
?: return null
val runnerAndConfigurationSettings = environment.runnerAndConfigurationSettings!!
val gradleRunConfiguration = runnerAndConfigurationSettings.configuration as ExternalSystemRunConfiguration
val gradlePath = GradleProjectResolverUtil.getGradlePath(module) ?: return null
val sourceSetName = when {
GradleConstants.GRADLE_SOURCE_SET_MODULE_TYPE_KEY == ExternalSystemApiUtil.getExternalModuleType(
module) -> GradleProjectResolverUtil.getSourceSetName(module)
ModuleRootManager.getInstance(module).fileIndex.isInTestSourceContent(virtualFile) -> "test"
else -> "main"
} ?: return null
val initScript = generateInitScript(applicationConfiguration, project, module, params, gradlePath,
runAppTaskName, mainClass, javaExePath, sourceSetName, javaModuleName)
gradleRunConfiguration.putUserData<String>(GradleTaskManager.INIT_SCRIPT_KEY, initScript)
gradleRunConfiguration.putUserData<String>(GradleTaskManager.INIT_SCRIPT_PREFIX_KEY, runAppTaskName)
// reuse all before tasks except 'Make' as it doesn't make sense for delegated run
gradleRunConfiguration.beforeRunTasks = RunManagerImpl.getInstanceImpl(project).getBeforeRunTasks(applicationConfiguration)
.filter { it.providerId !== CompileStepBeforeRun.ID }
return environment
}
companion object {
private val LOG = Logger.getInstance(GradleApplicationEnvironmentProvider::class.java)
private fun createEscapedParameters(parameters: List<String>, prefix: String): String {
val result = StringBuilder()
for (parameter in parameters) {
if (StringUtil.isEmpty(parameter)) continue
val escaped = StringUtil.escapeChars(parameter, '\\', '"', '\'')
result.append(prefix).append(" '").append(escaped).append("'\n")
}
return result.toString()
}
private fun findJavaModuleName(sdk: Sdk, module: JavaRunConfigurationModule, mainClass: PsiClass): String? {
return if (JavaSdkUtil.isJdkAtLeast(sdk, JavaSdkVersion.JDK_1_9)) {
DumbService.getInstance(module.project).computeWithAlternativeResolveEnabled<PsiJavaModule, RuntimeException> {
JavaModuleGraphUtil.findDescriptorByElement(module.findClass(mainClass.qualifiedName))
}?.name ?: return null
}
else null
}
private fun generateInitScript(applicationConfiguration: ApplicationConfiguration,
project: Project,
module: Module,
params: JavaParameters,
gradlePath: String,
runAppTaskName: String,
mainClass: PsiClass,
javaExePath: String,
sourceSetName: String,
javaModuleName: String?): String {
val workingDir = ProgramParametersUtil.getWorkingDir(applicationConfiguration, project, module)?.let {
FileUtil.toSystemIndependentName(it)
}
val argsString = createEscapedParameters(params.programParametersList.parameters, "args") +
createEscapedParameters(params.vmParametersList.parameters, "jvmArgs")
val useManifestJar = applicationConfiguration.shortenCommandLine === ShortenCommandLine.MANIFEST
val useArgsFile = applicationConfiguration.shortenCommandLine === ShortenCommandLine.ARGS_FILE
var useClasspathFile = applicationConfiguration.shortenCommandLine === ShortenCommandLine.CLASSPATH_FILE
var intelliJRtPath: String? = null
if (useClasspathFile) {
try {
intelliJRtPath = PathUtil.getCanonicalPath(
PathManager.getJarPathForClass(Class.forName("com.intellij.rt.execution.CommandLineWrapper")))
}
catch (t: Throwable) {
LOG.warn("Unable to use classpath file", t)
useClasspathFile = false
}
}
// @formatter:off
@Suppress("UnnecessaryVariable")
// @Language("Groovy")
val initScript = """
def gradlePath = '$gradlePath'
def runAppTaskName = '$runAppTaskName'
def mainClass = '${mainClass.qualifiedName}'
def javaExePath = '$javaExePath'
def _workingDir = ${if (workingDir.isNullOrEmpty()) "null\n" else "'$workingDir'\n"}
def sourceSetName = '$sourceSetName'
def javaModuleName = ${if (javaModuleName == null) "null\n" else "'$javaModuleName'\n"}
${if (useManifestJar) "gradle.addListener(new ManifestTaskActionListener(runAppTaskName))\n" else ""}
${if (useArgsFile) "gradle.addListener(new ArgFileTaskActionListener(runAppTaskName))\n" else ""}
${if (useClasspathFile && intelliJRtPath != null) "gradle.addListener(new ClasspathFileTaskActionListener(runAppTaskName, mainClass, '$intelliJRtPath'))\n " else ""}
allprojects {
afterEvaluate { project ->
if(project.path == gradlePath && project?.convention?.findPlugin(JavaPluginConvention)) {
def overwrite = project.tasks.findByName(runAppTaskName) != null
project.tasks.create(name: runAppTaskName, overwrite: overwrite, type: JavaExec) {
if (javaExePath) executable = javaExePath
classpath = project.sourceSets[sourceSetName].runtimeClasspath
main = mainClass
$argsString
if(_workingDir) workingDir = _workingDir
standardInput = System.in
if(javaModuleName) {
inputs.property('moduleName', javaModuleName)
doFirst {
jvmArgs += [
'--module-path', classpath.asPath,
'--module', javaModuleName + '/' + mainClass
]
classpath = files()
}
}
}
}
}
}
""" + (if (useManifestJar || useArgsFile || useClasspathFile) """
import org.gradle.api.execution.TaskActionListener
import org.gradle.api.Task
import org.gradle.api.tasks.JavaExec
abstract class RunAppTaskActionListener implements TaskActionListener {
String myTaskName
File myClasspathFile
RunAppTaskActionListener(String taskName) {
myTaskName = taskName
}
void beforeActions(Task task) {
if(!(task instanceof JavaExec) || task.name != myTaskName) return
myClasspathFile = patchTaskClasspath(task)
}
void afterActions(Task task) {
if(myClasspathFile != null) { myClasspathFile.delete() }
}
abstract File patchTaskClasspath(JavaExec task)
}
""" else "") + (if (useManifestJar) """
import org.gradle.api.tasks.JavaExec
import java.util.jar.Attributes
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
class ManifestTaskActionListener extends RunAppTaskActionListener {
ManifestTaskActionListener(String taskName) {
super(taskName)
}
File patchTaskClasspath(JavaExec task) {
Manifest manifest = new Manifest()
Attributes attributes = manifest.getMainAttributes()
attributes.put(Attributes.Name.MANIFEST_VERSION, '1.0')
attributes.putValue('Class-Path', task.classpath.files.collect {it.toURI().toURL().toString()}.join(' '))
File file = File.createTempFile('generated-', '-manifest')
def oStream = new JarOutputStream(new FileOutputStream(file), manifest)
oStream.putNextEntry(new ZipEntry('META-INF/'))
oStream.close()
task.classpath = task.project.files(file.getAbsolutePath())
return file
}
}
""" else "") + (if (useArgsFile) """
import org.gradle.api.tasks.JavaExec
import org.gradle.process.CommandLineArgumentProvider
class ArgFileTaskActionListener extends RunAppTaskActionListener {
ArgFileTaskActionListener(String taskName) {
super(taskName)
}
File patchTaskClasspath(JavaExec task) {
File file = File.createTempFile('generated-', '-argFile')
def writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), 'UTF-8'))
def lineSep = System.getProperty('line.separator')
writer.print('-classpath' + lineSep)
writer.print(quoteArg(task.classpath.asPath))
writer.print(lineSep)
writer.close()
task.jvmArgs('@' + file.absolutePath)
task.classpath = task.project.files([])
return file
}
private static String quoteArg(String arg) {
String specials = ' #\'\"\n\r\t\f'
if (specials.find { arg.indexOf(it) != -1 } == null) return arg
StringBuilder sb = new StringBuilder(arg.length() * 2)
for (int i = 0; i < arg.length(); i++) {
char c = arg.charAt(i)
if (c == ' ' as char || c == '#' as char || c == '\'' as char) sb.append('"').append(c).append('"')
else if (c == '"' as char) sb.append("\"\\\"\"")
else if (c == '\n' as char) sb.append("\"\\n\"")
else if (c == '\r' as char) sb.append("\"\\r\"")
else if (c == '\t' as char) sb.append("\"\\t\"")
else if (c == '\f' as char) sb.append("\"\\f\"")
else sb.append(c)
}
return sb.toString()
}}
""" else "") + if (useClasspathFile) """
import org.gradle.api.tasks.JavaExec
import org.gradle.process.CommandLineArgumentProvider
class ClasspathFileTaskActionListener extends RunAppTaskActionListener {
String myMainClass
String myIntelliJRtPath
ClasspathFileTaskActionListener(String taskName, String mainClass, String intelliJRtPath) {
super(taskName)
myMainClass = mainClass
myIntelliJRtPath = intelliJRtPath
}
File patchTaskClasspath(JavaExec task) {
File file = File.createTempFile('generated-', '-classpathFile')
def writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), 'UTF-8'))
task.classpath.files.each { writer.println(it.path) }
writer.close()
List args = [file.absolutePath, myMainClass] as List
args.addAll(task.args)
task.args = []
task.argumentProviders.add({ return args } as CommandLineArgumentProvider)
task.main = 'com.intellij.rt.execution.CommandLineWrapper'
task.classpath = task.project.files([myIntelliJRtPath])
return file
}
}
""" else ""
// @formatter:on
return initScript
}
}
}