@@ -34,9 +34,7 @@ class ScoveragePlugin(val global: Global) extends Plugin {
34
34
options.dataDir = opt.substring(" dataDir:" .length)
35
35
} else if (opt.startsWith(" extraAfterPhase:" ) || opt.startsWith(" extraBeforePhase:" )) {
36
36
// skip here, these flags are processed elsewhere
37
- } else if (opt.startsWith(" classpathSubdir:" )){
38
- options.classpathSubdir = opt.substring(" classpathSubdir:" .length)
39
- }else if (opt.startsWith(" writeToClasspath:" )) {
37
+ } else if (opt.startsWith(" writeToClasspath:" )) {
40
38
try {
41
39
options.writeToClasspath = opt.substring(" writeToClasspath:" .length).toBoolean
42
40
} catch {
@@ -56,10 +54,10 @@ class ScoveragePlugin(val global: Global) extends Plugin {
56
54
}
57
55
58
56
override val optionsHelp : Option [String ] = Some (Seq (
59
- " -P:scoverage:writeToClasspath:<boolean> if true, use the environment variable to store measurements" +
60
- " and store instruments to the output classpath where compiler emits classfiles." +
57
+ " -P:scoverage:writeToClasspath:<boolean> if true, use the system property variable [scoverage_measurement_path]" +
58
+ " to store measurements " +
59
+ " and store instruments file to the output classpath where compiler emits classfiles. " +
61
60
" Overrides the datadir with the output classpath." ,
62
- " -P:scoverage:classpathSubdir:<pathtosubdir> subdir to attach to classpath. Default: META-INF/scoverage" ,
63
61
64
62
" -P:scoverage:dataDir:<pathtodatadir> where the coverage files should be written\n " ,
65
63
" -P:scoverage:excludedPackages:<regex>;<regex> semicolon separated list of regexs for packages to exclude" ,
@@ -101,13 +99,12 @@ class ScoverageOptions {
101
99
102
100
// Adding additional option for https://github.com/scoverage/scalac-scoverage-plugin/issues/265
103
101
// If the option is false, scoverage works the usual way.
104
- // If the option is true, the instruments are stored on to the classpath and
105
- // the measurements are written to the directory specified by the environment variable
106
- // `SCOVERAGE_MEASUREMENT_PATH `. This means setting [writeToClasspath] to true overrides the [dataDir]
102
+ // If the option is true, the instrument files [scoverage.coverage] are stored on to the classpath and
103
+ // the measurement files are written to the directory specified by the system property
104
+ // `scoverage_measurement_path `. This means setting [writeToClasspath] to true overrides the [dataDir]
107
105
// as the [dataDir] will now point to the output classpath of the compiler.
108
106
// By default, the option is set to false.
109
107
var writeToClasspath : Boolean = false
110
- var classpathSubdir : String = " META-INF/scoverage"
111
108
}
112
109
113
110
class ScoverageInstrumentationComponent (val global : Global , extraAfterPhase : Option [String ], extraBeforePhase : Option [String ])
@@ -133,6 +130,27 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
133
130
private var options : ScoverageOptions = new ScoverageOptions ()
134
131
private var coverageFilter : CoverageFilter = AllCoverageFilter
135
132
133
+ // This [targetId] is used below in case [writeToClasspath] option
134
+ // is true. It is used for:
135
+ // 1. Creating a unique subdir under the classpath to store the instrument files, i.e
136
+ // path to instrument files will be `< classpath >/META-INF/scoverage/< targetId >/scoverage.coverage`
137
+ // 2. Instrumenting the binaries with the [targetId], i.e binaries will be instrumented with
138
+ // `Invoker.invokedWriteToClasspath(< statement id >, < targetId >)`
139
+ // Consequently, at runtime, we can get the "correct" instrument file located at `.../< targetId >/scoverage.coverage`
140
+ // from the classpath and store it with its appropriate measurements file.
141
+ private var targetId : String = " "
142
+
143
+ // used for generating the targetId.
144
+ def md5HashString (s : String ): String = {
145
+ import java .security .MessageDigest
146
+ import java .math .BigInteger
147
+ val md = MessageDigest .getInstance(" MD5" )
148
+ val digest = md.digest(s.getBytes)
149
+ val bigInt = new BigInteger (1 ,digest)
150
+ val hashedString = bigInt.toString(16 )
151
+ hashedString
152
+ }
153
+
136
154
def setOptions (options : ScoverageOptions ): Unit = {
137
155
138
156
this .options = options
@@ -142,7 +160,11 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
142
160
// i.e to the directory where compiler is going to output the classfiles.
143
161
if (options.writeToClasspath){
144
162
settings.outputDirs.getSingleOutput match {
145
- case Some (dest) => options.dataDir = s " ${dest.toString()}/ ${options.classpathSubdir}"
163
+ case Some (dest) =>
164
+ targetId = md5HashString(s " ${dest.toString()}" )
165
+ // Setting [dataDir] to `< classpath >/META-INF/scoverage/< targetId >`.
166
+ // This is where the instrument file [scoverage.coverage] for this target will now be stored.
167
+ options.dataDir = s " ${dest.toString()}/ ${Constants .ClasspathSubdir }/ $targetId"
146
168
case None => throw new RuntimeException (" No output classpath specified." )
147
169
}
148
170
}
@@ -173,8 +195,8 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
173
195
reporter.echo(s " Will write measurement data to [ ${ options.dataDir }] " )
174
196
}
175
197
else {
176
- reporter.echo(s " Will write measurements data to the directory specified by environment " +
177
- s " variable SCOVERAGE_MEASUREMENT_PATH at runtime. " )
198
+ reporter.echo(s " Will write measurements data to the directory specified by system " +
199
+ s " variable scoverage_measurement_path at runtime. " )
178
200
}
179
201
}
180
202
}
@@ -199,49 +221,41 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
199
221
def safeLine (tree : Tree ): Int = if (tree.pos.isDefined) tree.pos.line else - 1
200
222
def safeSource (tree : Tree ): Option [SourceFile ] = if (tree.pos.isDefined) Some (tree.pos.source) else None
201
223
202
- def invokeCall (id : Int ): Tree = {
224
+ var termName : String = " "
225
+ var secondArg : String = " "
226
+ if (options.writeToClasspath){
203
227
/**
204
- * We still pass in [options.dataDir] as one of the instruments with
205
- * [invokedUseEnvironment] as it helps differentiating the source
206
- * files at runtime, i.e helps in creating a unique subdir for each source file.
228
+ * We pass in [targetId] as one of the instruments with
229
+ * [invokedUseEnvironment] as it helps in grabbing the "correct" instruments
230
+ * file from the classpath at runtime. It also helps in
231
+ * differentiating the targets at runtime, i.e helps in creating a unique subdir for each target.
207
232
*/
208
- if (options.writeToClasspath){
209
- Apply (
233
+ termName = " invokedWriteToClasspath"
234
+ secondArg = targetId
235
+ }
236
+ else {
237
+ termName = " invoked"
238
+ secondArg = options.dataDir
239
+ }
240
+
241
+ def invokeCall (id : Int ): Tree = {
242
+ Apply (
243
+ Select (
210
244
Select (
211
- Select (
212
- Ident (" scoverage" ),
213
- newTermName(" Invoker" )
214
- ),
215
- newTermName(" invokedWriteToClasspath" )
245
+ Ident (" scoverage" ),
246
+ newTermName(" Invoker" )
216
247
),
217
- List (
218
- Literal (
219
- Constant (id)
220
- ),
221
- Literal (
222
- Constant (options.dataDir)
223
- )
224
- )
225
- )
226
- }else {
227
- Apply (
228
- Select (
229
- Select (
230
- Ident (" scoverage" ),
231
- newTermName(" Invoker" )
232
- ),
233
- newTermName(" invoked" )
248
+ newTermName(termName)
249
+ ),
250
+ List (
251
+ Literal (
252
+ Constant (id)
234
253
),
235
- List (
236
- Literal (
237
- Constant (id)
238
- ),
239
- Literal (
240
- Constant (options.dataDir)
241
- )
254
+ Literal (
255
+ Constant (secondArg)
242
256
)
243
257
)
244
- }
258
+ )
245
259
}
246
260
247
261
override def transform (tree : Tree ) = process(tree)
0 commit comments