Skip to content

Commit

Permalink
Merge pull request #1006 from gradle/breskeby/script-compile-build-ops
Browse files Browse the repository at this point in the history
Initial support for script compile build operations
  • Loading branch information
bamboo committed Aug 7, 2018
2 parents 166a26c + 719e6cc commit ce5b301
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 29 deletions.
Expand Up @@ -119,6 +119,8 @@ class Interpreter(val host: Host) {

fun hashOf(classPath: ClassPath): HashCode

fun runCompileBuildOperation(scriptPath: String, stage: String, action: () -> String): String

val implicitImports: List<String>
}

Expand Down Expand Up @@ -265,9 +267,9 @@ class Interpreter(val host: Host) {
residualProgramCompilerFor(
sourceHash,
outputDir,
targetScope.parent,
programKind,
programTarget
programTarget,
host.compilationClassPathOf(targetScope.parent)
).compile(residualProgram)
}
}
Expand Down Expand Up @@ -313,18 +315,20 @@ class Interpreter(val host: Host) {
fun residualProgramCompilerFor(
sourceHash: HashCode,
outputDir: File,
classLoaderScopeForClassPath: ClassLoaderScope,
programKind: ProgramKind,
programTarget: ProgramTarget
programTarget: ProgramTarget,
classPath: ClassPath
): ResidualProgramCompiler =

ResidualProgramCompiler(
outputDir,
host.compilationClassPathOf(classLoaderScopeForClassPath),
classPath,
sourceHash,
programKind,
programTarget,
host.implicitImports)
host.implicitImports,
interpreterLogger,
host::runCompileBuildOperation)

private
val defaultProgramHost = ProgramHost()
Expand Down Expand Up @@ -467,7 +471,9 @@ class Interpreter(val host: Host) {
sourceHash,
programKind,
programTarget,
host.implicitImports
host.implicitImports,
interpreterLogger,
host::runCompileBuildOperation
).emitStage2ProgramFor(
scriptFile,
originalScriptPath
Expand Down
Expand Up @@ -62,6 +62,10 @@ import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies


internal
typealias CompileBuildOperationRunner = (String, String, () -> String) -> String


/**
* Compiles the given [residual program][ResidualProgram] to an [ExecutableProgram] subclass named `Program`
* stored in the given [outputDir].
Expand All @@ -74,7 +78,8 @@ class ResidualProgramCompiler(
private val programKind: ProgramKind,
private val programTarget: ProgramTarget,
private val implicitImports: List<String> = emptyList(),
private val logger: Logger = interpreterLogger
private val logger: Logger = interpreterLogger,
private val compileBuildOperationRunner: CompileBuildOperationRunner = ::defaultCompileBuildOperationRunner
) {

fun compile(program: ResidualProgram) = when (program) {
Expand Down Expand Up @@ -213,7 +218,7 @@ class ResidualProgramCompiler(

fun emitStage2ProgramFor(scriptFile: File, originalPath: String) {

val precompiledScriptClass = compileScript(scriptFile, originalPath, stage2ScriptDefinition)
val precompiledScriptClass = compileScript(scriptFile, originalPath, stage2ScriptDefinition, "BODY")

program<ExecutableProgram> {

Expand Down Expand Up @@ -499,21 +504,23 @@ class ResidualProgramCompiler(
fun compileStage1(source: ProgramSource, scriptDefinition: KotlinScriptDefinition): String {
withTemporaryScriptFileFor(source.path, source.text) { scriptFile ->
val originalScriptPath = source.path
return compileScript(scriptFile, originalScriptPath, scriptDefinition)
return compileScript(scriptFile, originalScriptPath, scriptDefinition, "CLASSPATH")
}
}

private
fun compileScript(scriptFile: File, originalPath: String, scriptDefinition: KotlinScriptDefinition): String =
compileKotlinScriptToDirectory(
outputDir,
scriptFile,
scriptDefinition,
classPath.asFiles,
messageCollectorFor(logger) { path ->
if (path == scriptFile.path) originalPath
else path
})
fun compileScript(scriptFile: File, originalPath: String, scriptDefinition: KotlinScriptDefinition, stage: String): String =
compileBuildOperationRunner(originalPath, stage) {
compileKotlinScriptToDirectory(
outputDir,
scriptFile,
scriptDefinition,
classPath.asFiles,
messageCollectorFor(logger) { path ->
if (path == scriptFile.path) originalPath
else path
})
}

private
val stage1ScriptDefinition
Expand Down Expand Up @@ -559,6 +566,11 @@ class ResidualProgramCompiler(
}


private
fun defaultCompileBuildOperationRunner(scriptPath: String, stage: String, operation: () -> String) =
operation()


private
fun publicClass(name: String, superName: String = "java/lang/Object", interfaces: Array<String>? = null, classBody: ClassWriter.() -> Unit = {}) =
ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES).run {
Expand Down
Expand Up @@ -29,6 +29,7 @@ import org.gradle.initialization.ClassLoaderScopeRegistry
import org.gradle.internal.classloader.ClasspathHasher

import org.gradle.internal.logging.progress.ProgressLoggerFactory
import org.gradle.internal.operations.BuildOperationExecutor

import org.gradle.kotlin.dsl.cache.ScriptCache
import org.gradle.kotlin.dsl.support.EmbeddedKotlinProvider
Expand Down Expand Up @@ -84,7 +85,8 @@ object BuildServices {
classPathHasher: ClasspathHasher,
scriptCache: ScriptCache,
implicitImports: ImplicitImports,
progressLoggerFactory: ProgressLoggerFactory
progressLoggerFactory: ProgressLoggerFactory,
buildOperationExecutor: BuildOperationExecutor
): KotlinScriptEvaluator =

StandardKotlinScriptEvaluator(
Expand All @@ -99,7 +101,8 @@ object BuildServices {
classPathHasher,
scriptCache,
implicitImports,
progressLoggerFactory)
progressLoggerFactory,
buildOperationExecutor)

private
fun versionedJarCacheFor(jarCache: GeneratedGradleJarCache): JarCache =
Expand Down
Expand Up @@ -31,9 +31,19 @@ import org.gradle.groovy.scripts.internal.ScriptSourceHasher
import org.gradle.internal.classloader.ClasspathHasher
import org.gradle.internal.classpath.ClassPath
import org.gradle.internal.classpath.DefaultClassPath

import org.gradle.internal.hash.HashCode

import org.gradle.internal.logging.progress.ProgressLoggerFactory

import org.gradle.internal.operations.BuildOperationContext
import org.gradle.internal.operations.BuildOperationDescriptor
import org.gradle.internal.operations.BuildOperationExecutor
import org.gradle.internal.operations.CallableBuildOperation

import org.gradle.internal.scripts.CompileScriptBuildOperationType.Details
import org.gradle.internal.scripts.CompileScriptBuildOperationType.Result

import org.gradle.kotlin.dsl.cache.ScriptCache

import org.gradle.kotlin.dsl.execution.EvalOption
Expand Down Expand Up @@ -84,7 +94,8 @@ class StandardKotlinScriptEvaluator(
private val classPathHasher: ClasspathHasher,
private val scriptCache: ScriptCache,
private val implicitImports: ImplicitImports,
private val progressLoggerFactory: ProgressLoggerFactory
private val progressLoggerFactory: ProgressLoggerFactory,
private val buildOperationExecutor: BuildOperationExecutor
) : KotlinScriptEvaluator {

override fun evaluate(
Expand Down Expand Up @@ -134,9 +145,26 @@ class StandardKotlinScriptEvaluator(
Interpreter(InterpreterHost())
}

private
inner class InterpreterHost : Interpreter.Host {

override fun runCompileBuildOperation(scriptPath: String, stage: String, action: () -> String): String =

buildOperationExecutor.call(object : CallableBuildOperation<String> {

override fun call(context: BuildOperationContext): String =
action().also {
context.setResult(object : Result {})
}

override fun description(): BuildOperationDescriptor.Builder {
val name = "Compile script ${scriptPath.substringAfterLast(File.separator)} ($stage)"
return BuildOperationDescriptor.displayName(name).name(name).details(object : Details {
override fun getStage(): String = stage
override fun getLanguage(): String = "KOTLIN"
})
}
})

override fun setupEmbeddedKotlinFor(scriptHost: KotlinScriptHost<*>) {
setupEmbeddedKotlinForBuildscript(scriptHost.scriptHandler)
}
Expand Down
Expand Up @@ -37,7 +37,7 @@ abstract class AbstractScriptCachingIntegrationTest : AbstractIntegrationTest()

protected
fun gradleRunnerForCacheInspection(vararg arguments: String) =
gradleRunnerForArguments("-d", *arguments)
gradleRunnerForArguments("-d", "-Dorg.gradle.internal.operations.trace=${newFile("operation-trace")}", *arguments)

protected
fun buildWithAnotherDaemon(vararg arguments: String): BuildResult =
Expand Down
Expand Up @@ -66,9 +66,9 @@ sealed class CachedScript {
class CompilationStage(
programTarget: ProgramTarget,
programKind: ProgramKind,
stage: String,
val stage: String,
sourceDescription: String,
file: File,
val file: File,
val enabled: Boolean = true
) : CachedScript() {

Expand Down
Expand Up @@ -31,6 +31,8 @@ import org.gradle.testkit.runner.BuildResult
import org.junit.ClassRule
import org.junit.Test

import java.io.File


@LeaksFileHandles("""
Daemons hold their daemon log file open after the build has finished, debug logging exacerbates this.
Expand All @@ -55,7 +57,12 @@ class ScriptCachingIntegrationTest : AbstractScriptCachingIntegrationTest() {

// when: first use
buildForCacheInspection("help").apply {

compilationTrace(projectRoot) {
assertScriptCompile(settingsFile.stage1)
assertNoScriptCompile(settingsFile.stage2)
assertNoScriptCompile(rootBuildFile.stage1)
assertScriptCompile(rootBuildFile.stage2)
}
// then: single compilation and classloading
compilationCache {
misses(settingsFile, rootBuildFile, leftBuildFile)
Expand All @@ -70,6 +77,12 @@ class ScriptCachingIntegrationTest : AbstractScriptCachingIntegrationTest() {
// when: second use
buildForCacheInspection("help").apply {

compilationTrace(projectRoot) {
assertNoScriptCompile(settingsFile.stage1)
assertNoScriptCompile(settingsFile.stage2)
assertNoScriptCompile(rootBuildFile.stage1)
assertNoScriptCompile(rootBuildFile.stage2)
}
// then: no compilation nor class loading
compilationCache {
hits(leftBuildFile, rootBuildFile, rightBuildFile)
Expand Down Expand Up @@ -326,3 +339,37 @@ fun BuildResult.assertOccurrenceCountOf(actionDisplayName: String, stage: Cached
internal
fun String.occurrenceCountOf(string: String) =
split(string).size - 1


internal
fun compilationTrace(projectRoot: File, action: CompileTrace.() -> Unit) {
val file = File(projectRoot, "operation-trace-log.txt")
action(CompileTrace(file.readLines()))
}


internal
class CompileTrace(private val operations: List<String>) {

fun assertScriptCompile(stage: CachedScript.CompilationStage) {
val description = operationDescription(stage)
require(operations.any { it.contains(description) }) {
"Expecting operation `$description`!"
}
}

fun assertNoScriptCompile(stage: CachedScript.CompilationStage) {
val description = operationDescription(stage)
require(!operations.any { it.contains(description) }) {
"Unexpected operation `$description`!"
}
}

private
fun operationDescription(stage: CachedScript.CompilationStage) =
"Compile script ${stage.file.name} (${descriptionOf(stage)})"

private
fun descriptionOf(stage: CachedScript.CompilationStage) =
if (stage.stage == "stage1") "CLASSPATH" else "BODY"
}
Expand Up @@ -32,7 +32,6 @@ import org.gradle.api.internal.initialization.ClassLoaderScope
import org.gradle.groovy.scripts.ScriptSource

import org.gradle.internal.hash.HashCode

import org.gradle.internal.resource.TextResource
import org.gradle.internal.service.ServiceRegistry

Expand All @@ -42,6 +41,7 @@ import org.gradle.kotlin.dsl.fixtures.classLoaderFor
import org.junit.Test

import java.io.File

import java.net.URLClassLoader


Expand Down Expand Up @@ -101,6 +101,8 @@ class InterpreterTest : TestWithTempFiles() {

on { startCompilerOperation(any()) } doReturn compilerOperation

on { runCompileBuildOperation(any(), any(), any()) } doAnswer { it.getArgument<() -> String>(2)() }

on {
cachedDirFor(
any(),
Expand Down

0 comments on commit ce5b301

Please sign in to comment.