@@ -7,15 +7,28 @@ import org.gradle.caching.configuration.AbstractBuildCache
7
7
import org .gradle .caching .{BuildCacheEntryWriter , BuildCacheEntryReader , BuildCacheService , BuildCacheServiceFactory , BuildCacheKey }
8
8
import com .google .auth .oauth2 .GoogleCredentials
9
9
import com .google .cloud .storage .{Bucket , StorageException , StorageOptions , BlobId , BlobInfo }
10
+ import com .google .cloud .storage .Storage .{BlobWriteOption , BlobTargetOption }
10
11
import com .google .common .io .FileBackedOutputStream
11
12
import java .nio .channels .Channels
12
13
import java .time .OffsetDateTime
13
14
import java .time .temporal .ChronoUnit
14
15
15
16
16
- class GCSBuildCacheConfiguration (
17
- var gcsURL : String = " " ,
18
- ) extends AbstractBuildCache
17
+ // Gradle requirs a no-argument constructor. A one-argument Scala constructor with default arguments
18
+ // *does not work*.
19
+ class GCSBuildCacheConfiguration () extends AbstractBuildCache {
20
+ // Gradle requires literal setters and getters. Do not try to expose a mutable field directly to
21
+ // gradle, you will get errors like "Could not set unknown property 'gcsURL'"
22
+ private [this ] var _gcsURL : String = null
23
+ def setGcsURL (gcsURL : String ) = _gcsURL = gcsURL
24
+ def getGcsURL : String = _gcsURL
25
+ def getIsPush : Boolean = isPush()
26
+ def setIsPush (isPush : Boolean ) = setPush(isPush)
27
+ // Gradle cannot directly modify isEnabled/setEnabled because they do not follow the get/set
28
+ // pattern.
29
+ def getEnabled : Boolean = isEnabled()
30
+ override def setEnabled (enabled : Boolean ) = super .setEnabled(enabled)
31
+ }
19
32
20
33
class GCSBuildCache (
21
34
conf : GCSBuildCacheConfiguration
@@ -27,47 +40,59 @@ class GCSBuildCache(
27
40
.getService
28
41
29
42
override def store (key : BuildCacheKey , writer : BuildCacheEntryWriter ): Unit = {
30
- val path = conf.gcsURL + " /" + key.hashCode.toString
31
- val blobinfo = BlobInfo .newBuilder(BlobId .fromGsUtilUri(path)).build()
32
- val value = new FileBackedOutputStream (8 * 1024 * 1024 , true )
33
- writer.writeTo(value)
34
- val is = value.asByteSource().openBufferedStream()
35
43
try {
36
- storage.create(blobinfo, is)
37
- System .out.println(s " cache stored $key" )
44
+ val path = conf.getGcsURL + " /" + key.hashCode
45
+ val blobinfo = BlobInfo .newBuilder(BlobId .fromGsUtilUri(path)).build()
46
+ val value = new FileBackedOutputStream (8 * 1024 * 1024 , true )
47
+ writer.writeTo(value)
48
+ log.lifecycle(s " storing cache $key ${key.hashCode} $path" )
49
+ val is = value.asByteSource().openBufferedStream()
50
+ try {
51
+ val onlyCreateIfNotExists = BlobWriteOption .doesNotExist()
52
+ storage.create(blobinfo, is, onlyCreateIfNotExists)
53
+ log.lifecycle(s " cache stored $key ${key.hashCode} $path" )
54
+ } catch {
55
+ case exc : StorageException =>
56
+ throw new RuntimeException (s " $key ${key.hashCode} could not be stored in cache at $path" , exc)
57
+ } finally {
58
+ is.close()
59
+ }
38
60
} catch {
39
- case exc : StorageException =>
40
- throw new RuntimeException (s " $key could not be stored in cache at $path" , exc)
41
- } finally {
42
- is.close()
61
+ case exc : Exception =>
62
+ // Gradle will silence all exceptions and quietly disable the build cache, so we loudly
63
+ // print the exception.
64
+ exc.printStackTrace()
65
+ throw exc
43
66
}
44
67
}
45
68
46
69
override def load (key : BuildCacheKey , reader : BuildCacheEntryReader ): Boolean = {
47
- System .out.println(s " checking cache key $key" )
48
- val path = conf.gcsURL + " /" + key.hashCode.toString
70
+ val path = conf.getGcsURL + " /" + key.hashCode
49
71
val blobid = BlobId .fromGsUtilUri(path)
72
+ log.lifecycle(s " checking cache key $key ${key.hashCode} $path" )
50
73
try {
51
74
val blob = storage.get(blobid)
75
+ if (blob == null ) {
76
+ return false
77
+ }
52
78
reader.readFrom(Channels .newInputStream(blob.reader()))
53
- System .out.println (s " cache hit $key" )
79
+ log.lifecycle (s " cache hit $key ${key.hashCode} $path " )
54
80
55
81
if (blob.getCreateTimeOffsetDateTime().until(OffsetDateTime .now(), ChronoUnit .SECONDS ) > 24 * 60 * 60 ) {
56
82
val blobinfo = BlobInfo .newBuilder(blobid).build()
57
- System .out.println(s " will refreshing cache $key" )
58
- storage.create(blobinfo, blob.getContent())
59
- System .out.println(s " refreshed cache $key" )
83
+ log.lifecycle(s " will refreshing cache $key ${key.hashCode} $path" )
84
+ val onlyUpdateIfNoOneHasBeatenMeToIt = BlobTargetOption .generationMatch(blob.getGeneration)
85
+ storage.create(blobinfo, blob.getContent(), onlyUpdateIfNoOneHasBeatenMeToIt)
86
+ log.lifecycle(s " refreshed cache $key ${key.hashCode} $path" )
60
87
}
61
88
return true
62
89
} catch {
63
- case exc : StorageException if exc.getCode == 404 =>
64
- System .out.println( s " cache miss $key " )
65
- return false
66
- case exc : StorageException =>
67
- throw new RuntimeException ( s " $key could not be loaded from cache at $path " , exc)
90
+ case exc : Exception =>
91
+ // Gradle will silence all exceptions and quietly disable the build cache, so we loudly
92
+ // print the exception.
93
+ exc.printStackTrace()
94
+ throw exc
68
95
}
69
-
70
- return false
71
96
}
72
97
73
98
override def close (): Unit = {}
@@ -78,13 +103,13 @@ class GCSBuildCacheServiceFactory extends BuildCacheServiceFactory[GCSBuildCache
78
103
conf : GCSBuildCacheConfiguration ,
79
104
describer : BuildCacheServiceFactory .Describer
80
105
): BuildCacheService = {
81
- if (conf.gcsURL == null || conf.gcsURL == " " ) {
106
+ if (conf.getGcsURL == null || conf.getGcsURL == " " ) {
82
107
throw new GradleException (" gcsURL must be set." )
83
108
}
84
109
85
110
describer
86
111
.`type`(" Google Cloud Storage" )
87
- .config(" gcsURL" , conf.gcsURL )
112
+ .config(" gcsURL" , conf.getGcsURL )
88
113
89
114
return new GCSBuildCache (conf)
90
115
}
0 commit comments