Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scoverage work - add scoverage to OSS pants #8064

Merged
merged 37 commits into from
Jul 27, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3ef8021
adding scoverage 1/2
Jul 17, 2019
705a12b
adding scoverage 1/2
Jul 17, 2019
5ea8fc5
adding scoverage 1/2
Jul 17, 2019
fc48b64
removing scoverage target from BUILD.tools
Jul 18, 2019
21a5dd4
Update src/python/pants/backend/jvm/subsystems/scala_coverage_platfor…
sammy-1234 Jul 18, 2019
2429357
removing futures
Jul 18, 2019
f432d7b
changing name to scoverage_platform
Jul 19, 2019
c7f560a
adding scoverage dependency for unit tests to pass
Jul 20, 2019
c427e21
Scoverage (2/2) - adding report generator
Jul 20, 2019
324f59d
changing report generator's name
Jul 20, 2019
1a7e8fc
scoverage report generator
Jul 23, 2019
739032a
adding scoverage report gen
Jul 23, 2019
fc4ed94
testing with new report gen local publish
Jul 23, 2019
3212da7
changing scoverage to work with new reportGen
Jul 23, 2019
f9561dc
relocatiing modifications to scala library
Jul 23, 2019
3d913e3
playing with new report gen
Jul 24, 2019
3209a9f
adding scoverage runtime environment along with target filtering and …
Jul 24, 2019
3cd1b8b
removing report generator from here
Jul 24, 2019
3099ac7
Merge branch 'master' into scoverageWork
sammy-1234 Jul 24, 2019
66ace61
resolving unwanted file changes
Jul 24, 2019
3ff84cc
Merge branch 'scoverageWork' of https://github.com/sammy-1234/pants i…
Jul 24, 2019
a47ef75
minor changes to alignment
Jul 24, 2019
7bacd7b
minor changes to alignment
Jul 24, 2019
d9a94e1
minor changes to alignment
Jul 24, 2019
37295d6
adding log statements
Jul 25, 2019
f390ee4
improving test file for blacklisted targets
Jul 25, 2019
b15f45d
adding scoverage dependency to test_rsc_compile to resolve unit test …
Jul 25, 2019
ae22fed
adding extra args in constructor rather than as a property
Jul 25, 2019
e5b0a56
removing target's scoverage instance
Jul 25, 2019
12430dd
fixing blacklisted target specification
Jul 26, 2019
04065ac
cleaning checking of blacklisted targets; fixing scoverage test
Jul 26, 2019
e13edd8
removing option; removing target spec as an option
Jul 26, 2019
4e1b5c8
init change in scala_library; linting
Jul 26, 2019
02435cc
fixing lint again
Jul 26, 2019
ed7d4b0
trying to fix lint in test_scoverage_platform
Jul 27, 2019
7acd82c
changing args indentation in scoverage.py
Jul 27, 2019
d6a2dbc
running git hooks successfully
Jul 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.tools
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jar_library(name = 'scrooge-linter',
jar(org='com.twitter', name='scrooge-linter_2.11', rev=SCROOGE_REV)
])


sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved
# Google doesn't publish Kythe jars (yet?). So we publish them to a custom repo
# (https://github.com/toolchainlabs/binhost) for now. See build-support/ivy/ivysettings.xml
# for more info.
Expand Down
3 changes: 2 additions & 1 deletion src/python/pants/backend/jvm/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pants.backend.jvm.scala_artifact import ScalaArtifact
from pants.backend.jvm.subsystems.jar_dependency_management import JarDependencyManagementSetup
from pants.backend.jvm.subsystems.scala_platform import ScalaPlatform
from pants.backend.jvm.subsystems.scoverage_platform import ScoveragePlatform
from pants.backend.jvm.subsystems.shader import Shading
from pants.backend.jvm.targets.annotation_processor import AnnotationProcessor
from pants.backend.jvm.targets.benchmark import Benchmark
Expand Down Expand Up @@ -138,7 +139,7 @@ def build_file_aliases():


def global_subsystems():
return (ScalaPlatform,)
return (ScalaPlatform, ScoveragePlatform, )


# TODO https://github.com/pantsbuild/pants/issues/604 register_goals
Expand Down
11 changes: 11 additions & 0 deletions src/python/pants/backend/jvm/subsystems/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ python_library(
],
)

python_library(
name = 'scoverage_platform',
sources = ['scoverage_platform.py'],
dependencies = [
'src/python/pants/option',
'src/python/pants/subsystem',
'src/python/pants/java/jar',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra space?

'src/python/pants/backend/jvm/targets:jvm',
],
)

python_library(
name = 'shader',
sources = ['shader.py'],
Expand Down
125 changes: 125 additions & 0 deletions src/python/pants/backend/jvm/subsystems/scoverage_platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import os
from pants.build_graph.injectables_mixin import InjectablesMixin
from pants.subsystem.subsystem import Subsystem
from pants.java.jar.jar_dependency import JarDependency
from pants.backend.jvm.targets.jar_library import JarLibrary
from pants.build_graph.address import Address
from typing import Any, Dict, List, Optional, Union


SCOVERAGE = "scoverage"
blacklist_file = 'new_blacklist_scoverage'
sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved


class ScoveragePlatform(InjectablesMixin, Subsystem):
"""The scala coverage platform."""

options_scope = 'scoverage'

@classmethod
def register_options(cls, register):
super().register_options(register)
register('--enable-scoverage',
default=False,
type=bool,
help='Specifies whether to generate scoverage reports for scala test targets.'
stuhood marked this conversation as resolved.
Show resolved Hide resolved
'Default value is False')

register('--blacklist-file',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pants supports @file options natively, so you should make this option a list option:

register('--blacklist-targets',
  type=list,
  member_type=target_option,
  ..
)

And then someone who wants to put the list of targets in a file (in python list syntax) can pass --blacklist-targets=@some-blacklist-file.

You could also pass in a (string) list of regexes to match against target names.

default=blacklist_file,
sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved
type=str,
help='Path to files containing targets not to be instrumented.')

register('--scoverage-target-path',
default='//:scoverage',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this target is bootstrapped by default. If a user overrides this, the default //:scoverage target is still injected into the build graph.

type=str,
help='Path to the scoverage dependency.')

def scoverage_jar(self):
return [JarDependency(org='com.twitter.scoverage', name='scalac-scoverage-plugin_2.12',
rev='1.0.1-twitter'),
JarDependency(org='com.twitter.scoverage', name='scalac-scoverage-runtime_2.12',
rev='1.0.1-twitter')]

def injectables(self, build_graph):
specs_to_create = [
('scoverage', self.scoverage_jar),
]

for spec_key, create_jardep_func in specs_to_create:
spec = self.injectables_spec_for_key(spec_key)
target_address = Address.parse(spec)
if not build_graph.contains_address(target_address):
target_jars = create_jardep_func()
jars = target_jars if isinstance(target_jars, list) else [target_jars]
build_graph.inject_synthetic_target(target_address,
JarLibrary,
jars=jars,
scope='forced')
elif not build_graph.get_target(target_address).is_synthetic:
raise build_graph.ManualSyntheticTargetError(target_address)

@property
def injectables_spec_mapping(self):
return {
'scoverage': [f"{self.get_options().scoverage_target_path}"],
}

def get_scalac_plugins(self, target) -> List[str]:
"""
Adds 'scoverage' to scalac_plugins in case scoverage is enabled for that [target].
:return: modified scalac_plugins
"""

# Prevent instrumenting generated targets and targets in blacklist.
if target.identifier.startswith(".pants.d.gen") or self.is_blacklisted(target):
return target.payload.scalac_plugins

scalac_plugins = target.payload.scalac_plugins
if scalac_plugins:
scalac_plugins.append(SCOVERAGE)
else:
scalac_plugins = [SCOVERAGE]
return scalac_plugins

def get_scalac_plugin_args(self, target) -> Dict[str, List[str]]:
"""
Adds 'scoverage' to scalac_plugins_args in case scoverage is enabled for that [target].
:return: modified scalac_plugins_args
"""
scalac_plugin_args = target.payload.scalac_plugin_args
if scalac_plugin_args:
scalac_plugin_args.update(
{"scoverage": ["writeToClasspath:true", f"dataDir:{target.identifier}"]})
else:
scalac_plugin_args = {
"scoverage": ["writeToClasspath:true", f"dataDir:{target.identifier}"]
}
return scalac_plugin_args

def get_compiler_option_sets(self, target):
sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved
"""
Adds 'scoverage' to compiler_options_sets in case scoverage is enabled for that [target].
:return: modified compiler_option_sets
"""
compiler_option_sets = target.payload.compiler_option_sets
if compiler_option_sets:
list(compiler_option_sets).append(SCOVERAGE)
else:
compiler_option_sets = [SCOVERAGE]
return tuple(compiler_option_sets)

def is_blacklisted(self, target) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blacklist should be cached in-memory, currently the file is read per target.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you switch this to a list option (as above) that will be handled automatically.

"""
Checks if the [target] is blacklisted or not.
"""
if not os.path.exists(self.get_options().blacklist_file):
return False

if target.address.spec in open(self.get_options().blacklist_file).read():
return True
else:
return False
1 change: 1 addition & 0 deletions src/python/pants/backend/jvm/targets/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ python_library(
'3rdparty/python/twitter/commons:twitter.common.collections',
'src/python/pants/java/jar',
'src/python/pants/backend/jvm/subsystems:scala_platform',
'src/python/pants/backend/jvm/subsystems:scoverage_platform',
'src/python/pants/base:exceptions',
'src/python/pants/base:validation',
'src/python/pants/build_graph',
Expand Down
43 changes: 42 additions & 1 deletion src/python/pants/backend/jvm/targets/scala_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.backend.jvm.subsystems.scala_platform import ScalaPlatform
from pants.backend.jvm.subsystems.scoverage_platform import ScoveragePlatform
from pants.backend.jvm.targets.exportable_jvm_library import ExportableJvmLibrary
from pants.backend.jvm.targets.junit_tests import JUnitTests
from pants.base.exceptions import TargetDefinitionException
Expand All @@ -27,7 +28,7 @@ class ScalaLibrary(ExportableJvmLibrary):

@classmethod
def subsystems(cls):
return super().subsystems() + (ScalaPlatform, )
return super().subsystems() + (ScalaPlatform, ScoveragePlatform,)

def __init__(self, java_sources=None, payload=None, **kwargs):
"""
Expand All @@ -47,6 +48,8 @@ def __init__(self, java_sources=None, payload=None, **kwargs):
})
super().__init__(payload=payload, **kwargs)

self._scoverage = ScoveragePlatform.global_instance().get_options().enable_scoverage

@classmethod
def compute_injectable_specs(cls, kwargs=None, payload=None):
for spec in super().compute_injectable_specs(kwargs, payload):
Expand All @@ -65,6 +68,10 @@ def compute_dependency_specs(cls, kwargs=None, payload=None):
for spec in ScalaPlatform.global_instance().injectables_specs_for_key('scala-library'):
yield spec

if ScoveragePlatform.global_instance().get_options().enable_scoverage:
for spec in ScoveragePlatform.global_instance().injectables_specs_for_key('scoverage'):
yield spec

def get_jar_dependencies(self):
for jar in super().get_jar_dependencies():
yield jar
Expand All @@ -82,3 +89,37 @@ def java_sources(self):
raise TargetDefinitionException(self, 'No such java target: {}'.format(spec))
targets.append(target)
return targets

@property
def scalac_plugins(self):
"""The names of compiler plugins to use when compiling this target with scalac.
:return: See constructor.
:rtype: list of strings.
"""
if self._scoverage:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than fully overriding the set of plugins, this should merge both lists. Ditto the plugin args and options.

Copy link
Contributor Author

@sammy-1234 sammy-1234 Jul 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function get_scalac_plugins in scoverage_platform.py does that. It merges the list if initially non empty (otherwise creates a new one) Similarly, for plugin args and options, the merging happens inside the scoverage subsystem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. That's a bit confusing, because if someone were to change line 102 here, they would need to remember to change the subsystem code as well. IMO, the merging code should live in here to avoid that issue.

return ScoveragePlatform.global_instance().get_scalac_plugins(self)

return self.payload.scalac_plugins

@property
def scalac_plugin_args(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be better decoupling here rather than checking if self._scoverage,
possible suggestion could be something similar to: (plugins, may not not need to be passed in but could be a class variable e.g.)

def scalac_plugin_args(self, plugins):
for plugin in plugins:
  # supplement args

"""Map from scalac plugin name to list of args for that plugin.
:return: See constructor.
:rtype: map from string to list of strings.
"""
if self._scoverage:
return ScoveragePlatform.global_instance().get_scalac_plugin_args(self)

return self.payload.scalac_plugin_args

@property
def compiler_option_sets(self):
"""For every element in this list, enable the corresponding flags on compilation
of targets.
:return: See constructor.
:rtype: list
"""
if self._scoverage:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above regarding decoupling

return ScoveragePlatform.global_instance().get_compiler_option_sets(self)

return self.payload.compiler_option_sets
19 changes: 14 additions & 5 deletions src/python/pants/backend/jvm/tasks/coverage/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import os
import shutil

from pants.backend.jvm.subsystems.scoverage_platform import ScoveragePlatform
from pants.backend.jvm.tasks.coverage.cobertura import Cobertura
from pants.backend.jvm.tasks.coverage.scoverage import Scoverage
from pants.backend.jvm.tasks.coverage.engine import NoCoverage
from pants.backend.jvm.tasks.coverage.jacoco import Jacoco
from pants.subsystem.subsystem import Subsystem
Expand Down Expand Up @@ -57,14 +59,14 @@ class CodeCoverage(Subsystem):

@classmethod
def subsystem_dependencies(cls):
return super().subsystem_dependencies() + (Cobertura.Factory, Jacoco.Factory)
return super().subsystem_dependencies() + (Cobertura.Factory, Jacoco.Factory, Scoverage.Factory)

# TODO(jtrobec): move these to subsystem scope after deprecating
@staticmethod
def register_junit_options(register, register_jvm_tool):
register('--coverage', type=bool, fingerprint=True, help='Collect code coverage data.')
register('--coverage-processor', advanced=True, fingerprint=True,
choices=['cobertura', 'jacoco'], default=None,
choices=['cobertura', 'jacoco', 'scoverage'], default=None,
help="Which coverage processor to use if --coverage is enabled. If this option is "
"unset but coverage is enabled implicitly or explicitly, defaults to 'cobertura'."
"If this option is explicitly set, implies --coverage.")
Expand All @@ -83,18 +85,25 @@ def register_junit_options(register, register_jvm_tool):
# register options for coverage engines
# TODO(jtrobec): get rid of these calls when engines are dependent subsystems
Cobertura.register_junit_options(register, register_jvm_tool)
Scoverage.register_junit_options(register, register_jvm_tool)

class InvalidCoverageEngine(Exception):
"""Indicates an invalid coverage engine type was selected."""

def get_coverage_engine(self, task, output_dir, all_targets, execute_java):
options = task.get_options()
if options.coverage or options.coverage_processor or options.is_flagged('coverage_open'):
processor = options.coverage_processor
sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved
if ScoveragePlatform.global_instance().get_options().enable_scoverage:
processor = 'scoverage'

if options.coverage or processor or options.is_flagged('coverage_open'):
settings = CodeCoverageSettings.from_task(task, workdir=output_dir)
if options.coverage_processor in ('cobertura', None):
if processor in ('cobertura', None):
return Cobertura.Factory.global_instance().create(settings, all_targets, execute_java)
elif options.coverage_processor == 'jacoco':
elif processor == 'jacoco':
return Jacoco.Factory.global_instance().create(settings, all_targets, execute_java)
elif processor == 'scoverage':
return Scoverage.Factory.global_instance().create(settings, all_targets, execute_java)
else:
# NB: We should never get here since the `--coverage-processor` is restricted by `choices`,
# but for clarity.
Expand Down
Loading