Skip to content
This repository was archived by the owner on Aug 19, 2020. It is now read-only.

Let Project scripts be cached in the Gradle build cache #978

Merged
merged 11 commits into from
Jul 20, 2018
Merged
Prev Previous commit
Next Next commit
Let Project scripts be cached in the Gradle build cache
If the build cache is enabled and the Gradle property
`org.gradle.kotlin.dsl.caching.buildcache` is set to true.
  • Loading branch information
bamboo committed Jul 20, 2018
commit 2201e4667b9d53c257d4b3fda8e7df361154adc7
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fun buildAccessorsClassPathFor(project: Project, classPath: ClassPath) =
configuredProjectSchemaOf(project)?.let { projectSchema ->
val cacheDir =
scriptCacheOf(project)
.cacheDirFor(cacheKeyFor(projectSchema)) {
.cacheDirFor(cacheKeyFor(projectSchema)) { baseDir, _ ->
buildAccessorsJarFor(projectSchema, classPath, outputDir = baseDir)
}
AccessorsClassPath(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.kotlin.dsl.cache

import org.gradle.kotlin.dsl.support.normalisedPathRelativeTo

import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.InputStream
import java.io.OutputStream

import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream


internal
fun pack(inputDir: File, outputStream: OutputStream): Long {

var entryCount = 0L

val gzipOutputStream = GZIPOutputStream(outputStream)

DataOutputStream(gzipOutputStream).run {

val buffer = ByteArray(DEFAULT_BUFFER_SIZE)

inputDir.walkTopDown().drop(1).forEach { file ->

val path = file.normalisedPathRelativeTo(inputDir)
val isFile = file.isFile

writeUTF(path)
writeBoolean(isFile)

if (isFile) {
writeLong(file.length())
file.copyTo(this, buffer)
}

entryCount += 1
}

writeUTF("")
}

gzipOutputStream.finish()

return entryCount
}


internal
fun unpack(inputStream: InputStream, outputDir: File): Long =

DataInputStream(GZIPInputStream(inputStream)).run {

val buffer = ByteArray(DEFAULT_BUFFER_SIZE)

var entryCount = 0L

while (true) {

val path = readUTF()
if (path.isEmpty()) break

val isFile = readBoolean()

val file = File(outputDir, path)
if (isFile) {
val length = readLong()
copyTo(file, length, buffer)
} else {
file.mkdir()
}

entryCount += 1
}

entryCount
}


private
fun File.copyTo(out: OutputStream, buffer: ByteArray) {
inputStream().use { input ->
var read = input.read(buffer)
while (read >= 0) {
out.write(buffer, 0, read)
read = input.read(buffer)
}
}
}


private
fun InputStream.copyTo(file: File, length: Long, buffer: ByteArray) {
file.outputStream().use { output ->
var remaining = length
val bufferSize = buffer.size.toLong()
while (remaining > 0) {
val read = read(buffer, 0, remaining.coerceAtMost(bufferSize).toInt())
output.write(buffer, 0, read)
remaining -= read
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,21 @@ object BuildServices {
cacheKeyBuilder: CacheKeyBuilder,
cacheRepository: CacheRepository,
startParameters: StartParameter
) =
): ScriptCache {

ScriptCache(
val hasBuildCacheIntegration =
startParameters.isBuildCacheEnabled && startParameters.isKotlinDslBuildCacheEnabled

return ScriptCache(
cacheRepository,
cacheKeyBuilder,
startParameters.isRecompileScripts
startParameters.isRecompileScripts,
hasBuildCacheIntegration
)
}
}


private
val StartParameter.isKotlinDslBuildCacheEnabled: Boolean
get() = projectProperties.getOrDefault("org.gradle.kotlin.dsl.caching.buildcache", null) == "true"
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gradle.kotlin.dsl.cache

import org.gradle.caching.BuildCacheKey
import org.gradle.caching.internal.controller.BuildCacheLoadCommand
import org.gradle.caching.internal.controller.BuildCacheStoreCommand

import java.io.File
import java.io.InputStream
import java.io.OutputStream


class ScriptBuildCacheKey(
private val displayName: String,
private val cacheKey: String
) : BuildCacheKey {

override fun getDisplayName(): String = displayName

override fun getHashCode(): String = cacheKey
}


/**
* Loads a directory from the build cache.
*/
class LoadDirectory(
private val directory: File,
private val cacheKey: BuildCacheKey
) : BuildCacheLoadCommand<Unit> {

override fun getKey(): BuildCacheKey = cacheKey

override fun load(inputStream: InputStream): BuildCacheLoadCommand.Result<Unit> {

val entryCount = unpack(inputStream, directory)

return object : BuildCacheLoadCommand.Result<Unit> {
override fun getMetadata() = Unit
override fun getArtifactEntryCount(): Long = entryCount
}
}
}


/**
* Stores a directory in the build cache.
*/
class StoreDirectory(
private val directory: File,
private val cacheKey: BuildCacheKey
) : BuildCacheStoreCommand {

override fun getKey(): BuildCacheKey = cacheKey

override fun store(outputStream: OutputStream): BuildCacheStoreCommand.Result {

val entryCount = pack(directory, outputStream)

return BuildCacheStoreCommand.Result { entryCount }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.gradle.kotlin.dsl.cache

import org.gradle.cache.CacheRepository
import org.gradle.cache.PersistentCache

import org.gradle.cache.internal.CacheKeyBuilder
import org.gradle.cache.internal.CacheKeyBuilder.CacheKeySpec
Expand All @@ -35,27 +34,29 @@ class ScriptCache(
val cacheKeyBuilder: CacheKeyBuilder,

private
val recompileScripts: Boolean
val recompileScripts: Boolean,

val hasBuildCacheIntegration: Boolean
) {

fun cacheDirFor(
keySpec: CacheKeySpec,
properties: Map<String, Any?>? = null,
initializer: PersistentCache.() -> Unit
): File =

cacheRepository
.cache(cacheKeyFor(keySpec))
initializer: (File, String) -> Unit
): File {
val cacheKey = cacheKeyFor(keySpec)
return cacheRepository.cache(cacheKey)
.apply { properties?.let { withProperties(it) } }
.apply { if (recompileScripts) withValidator { false } }
.withInitializer(initializer)
.withInitializer { initializer(it.baseDir, cacheKey) }
.open().run {
close()
baseDir
}
}

private
fun cacheKeyFor(spec: CacheKeySpec) = cacheKeyBuilder.build(spec)
fun cacheKeyFor(spec: CacheKeySpec): String = cacheKeyBuilder.build(spec)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import org.gradle.api.internal.initialization.ScriptHandlerInternal
import org.gradle.api.internal.plugins.PluginAwareInternal

import org.gradle.cache.CacheOpenException
import org.gradle.cache.PersistentCache
import org.gradle.cache.internal.CacheKeyBuilder
import org.gradle.caching.internal.controller.BuildCacheController

import org.gradle.groovy.scripts.ScriptSource
import org.gradle.groovy.scripts.internal.ScriptSourceHasher
Expand All @@ -36,7 +36,10 @@ import org.gradle.internal.classpath.DefaultClassPath
import org.gradle.internal.hash.HashCode
import org.gradle.internal.logging.progress.ProgressLoggerFactory

import org.gradle.kotlin.dsl.cache.LoadDirectory
import org.gradle.kotlin.dsl.cache.ScriptBuildCacheKey
import org.gradle.kotlin.dsl.cache.ScriptCache
import org.gradle.kotlin.dsl.cache.StoreDirectory

import org.gradle.kotlin.dsl.execution.EvalOption
import org.gradle.kotlin.dsl.execution.EvalOptions
Expand All @@ -50,10 +53,12 @@ import org.gradle.kotlin.dsl.support.EmbeddedKotlinProvider
import org.gradle.kotlin.dsl.support.ImplicitImports
import org.gradle.kotlin.dsl.support.KotlinScriptHost
import org.gradle.kotlin.dsl.support.ScriptCompilationException
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.kotlin.dsl.support.transitiveClosureOf

import org.gradle.plugin.management.internal.DefaultPluginRequests
import org.gradle.plugin.management.internal.PluginRequests

import org.gradle.plugin.use.internal.PluginRequestApplicator

import java.io.File
Expand Down Expand Up @@ -210,19 +215,46 @@ class StandardKotlinScriptEvaluator(
accessorsClassPath?.let { baseCacheKey + it }
?: baseCacheKey

cacheDirFor(effectiveCacheKey) {
initializer(baseDir)
}
cacheDirFor(scriptHost, effectiveCacheKey, initializer)
} catch (e: CacheOpenException) {
throw e.cause as? ScriptCompilationException ?: e
}

private
fun cacheDirFor(cacheKeySpec: CacheKeyBuilder.CacheKeySpec, initializer: PersistentCache.() -> Unit): File =
scriptCache.cacheDirFor(cacheKeySpec, properties = cacheProperties, initializer = initializer)
fun cacheDirFor(
scriptHost: KotlinScriptHost<*>,
cacheKeySpec: CacheKeyBuilder.CacheKeySpec,
initializer: (File) -> Unit
): File =
scriptCache.cacheDirFor(cacheKeySpec, properties = cacheProperties) { baseDir, cacheKey ->

val cacheDir =
File(baseDir, "cache").apply { require(mkdir()) }

// TODO: Move BuildCacheController integration to ScriptCache
val cacheController =
if (scriptCache.hasBuildCacheIntegration) buildCacheControllerOf(scriptHost) else null

if (cacheController != null) {
val buildCacheKey = ScriptBuildCacheKey(scriptHost.scriptSource.displayName, cacheKey)
val existing = cacheController.load(LoadDirectory(cacheDir, buildCacheKey))
if (existing == null) {
initializer(cacheDir)
cacheController.store(StoreDirectory(cacheDir, buildCacheKey))
}
} else {
initializer(cacheDir)
}
}.resolve("cache")

private
fun buildCacheControllerOf(scriptHost: KotlinScriptHost<*>) =
(scriptHost.target as? Project)
?.serviceOf<BuildCacheController>()
?.takeIf { it.isEnabled }

private
val cacheProperties = mapOf("version" to "11")
val cacheProperties = mapOf("version" to "12")

private
val cacheKeyPrefix =
Expand Down
Loading