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 32 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/python/pants/backend/jvm/register.py
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
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',
'src/python/pants/backend/jvm/targets:jvm',
],
)

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

import logging
import re
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 pants.option.custom_types import target_option

logger = logging.getLogger(__name__)

SCOVERAGE = "scoverage"

class ScoveragePlatform(InjectablesMixin, Subsystem):
"""The scoverage 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. '
'Default value is False. If True, '
'implies --test-junit-coverage-processor=scoverage.')

register('--blacklist-targets',
type=list,
member_type=target_option,
help='List of targets not to be instrumented. Accepts Regex patterns. All '
'targets matching any of the patterns will not be instrumented. If no targets '
'are specified, all targets will be instrumented.')


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 {
# Target spec for scoverage plugin.
'scoverage': [f"//:scoverage"],
}


def is_blacklisted(self, target_address_spec) -> bool:
"""
Checks if the [target] is blacklisted or not.
"""
# No blacklisted targets specified.
if not self.get_options().blacklist_targets:
return False

for filter in self.get_options().blacklist_targets:
if re.search(filter, target_address_spec) is not None:
logger.debug(f"{target_address_spec} found in blacklist, not instrumented.")
return True
return False
1 change: 1 addition & 0 deletions src/python/pants/backend/jvm/targets/BUILD
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
60 changes: 57 additions & 3 deletions src/python/pants/backend/jvm/targets/scala_library.py
Expand Up @@ -2,12 +2,17 @@
# 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
from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.build_graph.address import Address
from pants.build_graph.target import Target


SCOVERAGE = "scoverage"


class ScalaLibrary(ExportableJvmLibrary):
Expand All @@ -27,9 +32,15 @@ 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):
@staticmethod
def skip_instrumentation(**kwargs):
return (Target.compute_target_id(kwargs['address']).startswith(".pants.d.gen") or
ScoveragePlatform.global_instance().is_blacklisted(kwargs['address'].spec))

def __init__(self, java_sources=None, scalac_plugins=None, scalac_plugin_args=None,
compiler_option_sets=None, payload=None, **kwargs):
"""
:param java_sources: Java libraries this library has a *circular*
dependency on.
Expand All @@ -45,7 +56,46 @@ def __init__(self, java_sources=None, payload=None, **kwargs):
payload.add_fields({
'java_sources': PrimitiveField(self.assert_list(java_sources, key_arg='java_sources')),
})
super().__init__(payload=payload, **kwargs)

if ScoveragePlatform.global_instance().get_options().enable_scoverage:
# Settings scalac_plugins
# Preventing instrumentation of generated targets or targets in Scoverage blacklist option.
if not self.skip_instrumentation(**kwargs):
if scalac_plugins:
scalac_plugins.append(SCOVERAGE)
else:
scalac_plugins = [SCOVERAGE]

# Setting scalac_plugin_args
if scalac_plugin_args:
scalac_plugin_args.update(
{
"scoverage": ["writeToClasspath:true",
f"dataDir:{Target.compute_target_id(kwargs['address'])}"]
})
else:
scalac_plugin_args = {
"scoverage": ["writeToClasspath:true",
f"dataDir:{Target.compute_target_id(kwargs['address'])}"]
}

# Setting compiler_option_sets
if compiler_option_sets:
list(compiler_option_sets).append(SCOVERAGE)
else:
compiler_option_sets = [SCOVERAGE]

super().__init__(payload=payload,
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Having two super calls is pretty weird, and in general this has resulted in a lot of nesting.

An alternative might be to do something like:

scalac_plugins = ... or []
scalac_plugin_args = ... or []
compiler_option_sets = ... or []
if ScoveragePlatform.global_instance().get_options().enable_scoverage:
  scalac_plugins.append(..)
  scalac_plugin_args.update(..)
  compiler_option_sets.update(..)
super(...)

scalac_plugins=scalac_plugins,
scalac_plugin_args=scalac_plugin_args,
compiler_option_sets=tuple(compiler_option_sets),
**kwargs)
else:
super().__init__(payload=payload,
scalac_plugins=scalac_plugins,
scalac_plugin_args=scalac_plugin_args,
compiler_option_sets=compiler_option_sets,
**kwargs)

@classmethod
def compute_injectable_specs(cls, kwargs=None, payload=None):
Expand All @@ -65,6 +115,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 Down
38 changes: 30 additions & 8 deletions src/python/pants/backend/jvm/tasks/coverage/manager.py
@@ -1,16 +1,20 @@
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
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
from pants.util.dirutil import safe_mkdir
from pants.util.strutil import safe_shlex_split

logger = logging.getLogger(__name__)

class CodeCoverageSettings:
"""A class containing settings for code coverage tasks."""
Expand Down Expand Up @@ -57,21 +61,23 @@ 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.")
"unset but coverage is enabled implicitly or explicitly, defaults to 'cobertura'. "
"If this option is explicitly set, implies --coverage. If this option is set to "
"scoverage, then first scoverage MUST be enabled by passing option "
"--scoverage-enable-scoverage.")
# We need to fingerprint this even though it nominally UI-only affecting option since the
# presence of this option alone can implicitly flag on `--coverage`.
register('--coverage-open', type=bool, fingerprint=True,
help='Open the generated HTML coverage report in a browser. Implies --coverage.')
help='Open the generated HTML coverage report in a browser. Implies --coverage ')

register('--coverage-jvm-options', advanced=True, type=list, fingerprint=True,
help='JVM flags to be added when running the coverage processor. For example: '
Expand All @@ -89,12 +95,28 @@ class InvalidCoverageEngine(Exception):

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'):
enable_scoverage = ScoveragePlatform.global_instance().get_options().enable_scoverage
processor = options.coverage_processor
sammy-1234 marked this conversation as resolved.
Show resolved Hide resolved

if (processor == 'scoverage' and not enable_scoverage):
raise self.InvalidCoverageEngine("Cannot set processor to scoverage without first enabling "
"scoverage (by passing --scoverage-enable-scoverage option)")

if enable_scoverage:
if processor not in (None, 'scoverage'):
raise self.InvalidCoverageEngine(f"Scoverage is enabled. "
f"Cannot use {processor} as the engine. Set engine to scoverage "
f"(--test-junit-coverage-processor=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