Skip to content

Instantly share code, notes, and snippets.

@LiuVII
Created March 31, 2019 02:21
Show Gist options
  • Save LiuVII/733bc113d072397eae8bf04417762375 to your computer and use it in GitHub Desktop.
Save LiuVII/733bc113d072397eae8bf04417762375 to your computer and use it in GitHub Desktop.
Files for "must set an options_scope class-level property" issue
# coding=utf-8
# Copyright 2015 Foursquare Labs Inc. All Rights Reserved.
from __future__ import absolute_import, division, print_function, unicode_literals
from collections import namedtuple
import os
import shutil
import subprocess
from pants.base.build_environment import get_buildroot
from pants.base.exceptions import TaskError
from pants.base.workunit import WorkUnitLabel
from pants.util.memo import memoized_property
from fsqio.pants.spindle.tasks.spindle_task import SpindleTask
class BuildSpindle(SpindleTask):
"""Build spindle in a shelled pants invocation before using it for limited codegen.
This task is specialized to build just one target - the spindle codegen binary. The spindle binary requires
spindle to do codegen for itself in order to build. This self-dependency has required this hijack of
the round engine by shelling into a separate pants invocation.
This task should have as few side-effects as possible!
It allows a sane workflow when modifying the Spindle source, by forcing the circular dependency to point to the
frozen checkout of the binary source.
This task should only be installed if you intend to modify Spindle source code or ssp files. If so, make sure your
build has access to the the frozen checkout at src/jvm/io/fsq/spindle/codegen/__shaded_for_spindle_bootstrap__ in
addition to the Spindle source code that you will be changing.
"""
options_scope = "build-spindle"
PANTS_SCRIPT_NAME = 'pants'
PantsResult = namedtuple('PantsResult', ['command', 'returncode'])
def __init__(self, *args, **kwargs):
print("type(self): {}".format(type(self)))
print("type(self).options_scope: {}".format(type(self).options_scope))
print(isinstance(b"test", str))
print(isinstance(u"test", str))
print("isinstance(type(self).options_scope, str): {}".format(isinstance(type(self).options_scope, str)))
super(BuildSpindle, self).__init__(*args, **kwargs)
@property
def cache_target_dirs(self):
return True
def run_pants_no_lock(self, command, workunit_name=None, **kwargs):
global_args = ['--quiet'] if self.get_options().quiet else []
if self.get_options().level == 'debug':
global_args.append('-ldebug')
global_args.extend([
'--no-pantsrc',
'--no-lock',
])
pants_script = os.path.join(get_buildroot(), self.PANTS_SCRIPT_NAME)
pants_command = [pants_script] + global_args + command
with self.context.new_workunit(name=workunit_name, labels=[WorkUnitLabel.RUN]) as workunit:
proc = subprocess.Popen(
pants_command,
stdout=workunit.output('stdout'),
stderr=workunit.output('stderr'),
**kwargs
)
proc.communicate()
return self.PantsResult(pants_command, proc.returncode)
@classmethod
def register_options(cls, register):
super(BuildSpindle, cls).register_options(register)
register(
'--shelled',
fingerprint=True,
advanced=True,
type=bool,
default=False,
help="Don't pass this flag, internal use only!",
)
@classmethod
def product_types(cls):
return ['spindle_binary']
@classmethod
def implementation_version(cls):
return super(BuildSpindle, cls).implementation_version() + [('BuildSpindle', 1)]
@memoized_property
def spindle_bundle_out(self):
# NOTE(omer): Not sure why this is necessary, but our bundle gets written to a different path from what's expected in Fsqio
# return os.path.join(self.get_options().pants_distdir, 'spindle-bundle', 'spindle.jar')
return os.path.join(self.get_options().pants_distdir, 'src.jvm.io.fsq.spindle.codegen.spindle-bundle', 'spindle.jar')
def execute(self):
# The 'shelled' option is only passed by this execute method and indicates a shelled run of pants.
if not self.get_options().shelled:
# This task is specialized to build just one target - the spindle source.
targets = [self.spindle_target]
# TODO: This invalidation is incomplete and should do the stuff done by the jvm_compile fingerprint
# strategy. But since this task is scheduled to complete before the classpath is resolved, this is tricky.
with self.invalidated(targets, invalidate_dependents=True) as invalidation_check:
targets = invalidation_check.all_vts
if targets and len(targets) != 1:
raise TaskError("There should only be one versioned target for the build_spindle task!"
"(was: {})".format(targets))
vt = targets[0]
invalid_vts_by_target = {vt.target: vt}
if not vt.valid:
args = [
'--no-cache-read', '--build-spindle-shelled', 'bundle', '--bundle-jvm-deployjar',
'--cache-bundle-jvm-read-from=[]', '--cache-bundle-jvm-write-to=[]',
]
args.append(self.get_options().spindle_codegen_binary)
results = self.run_pants_no_lock(args, workunit_name='spindle-build')
if results.returncode != 0:
# Purposefully not returning a message so the error from the shelled run can be surfaced.
raise TaskError()
spindle_bundle = self.spindle_bundle_out
spindle_binary = os.path.join(vt.results_dir, 'spindle-bundle.jar')
try:
shutil.copy(spindle_bundle, spindle_binary)
except Exception as e:
raise TaskError("Could not copy the spindle binary at {}:\n{}".format(spindle_bundle, e))
self.context.products.get('spindle_binary').add(vt.target, vt.results_dir).append('spindle-bundle.jar')
# coding=utf-8
# Copyright 2015 Foursquare Labs Inc. All Rights Reserved.
from __future__ import absolute_import, division, print_function, unicode_literals
import os
from pants.backend.jvm.targets.jvm_binary import JvmBinary
from pants.backend.jvm.tasks.nailgun_task import NailgunTask
from pants.base.exceptions import TaskError
from pants.build_graph.address import Address
from pants.option.custom_types import target_option
from pants.util.memo import memoized_property
from fsqio.pants.spindle.targets.ssp_template import SspTemplate
class SpindleTask(NailgunTask):
"""A base class to declare and verify options for spindle tasks."""
options_scope = "spindle-task"
def __init__(self, *args, **kwargs):
print("type(self): {}".format(type(self)))
print("type(self).options_scope: {}".format(type(self).options_scope))
print("isinstance(type(self).options_scope, str): {}".format(isinstance(type(self).options_scope, str)))
super(SpindleTask, self).__init__(*args, **kwargs)
class BadDependency(TaskError):
"""Raise when spindle will error due to missing dependencies."""
@classmethod
def register_options(cls, register):
super(SpindleTask, cls).register_options(register)
register(
'--spindle-codegen-binary',
fingerprint=True,
advanced=True,
type=target_option,
help='Use this Spindle source to generate code.',
)
@classmethod
def implementation_version(cls):
return super(SpindleTask, cls).implementation_version() + [('SpindleTask', 3)]
@property
def cache_target_dirs(self):
return True
@memoized_property
def spindle_target(self):
return self.get_spindle_target(
'spindle_codegen_binary',
self.get_options().spindle_codegen_binary,
JvmBinary,
)
def get_ssp_templates(self, template_target):
if not isinstance(template_target, SspTemplate):
raise TaskError(
'Spindle codegen requires being passed templates as SspTemplate targets (was: {})'.format(template_target)
)
return os.path.join(template_target.address.spec_path, template_target.entry_point)
def get_spindle_target(self, option_name, option_value, target_type):
return self.resolve_target(option_value, target_type)
def resolve_target(self, spec, target_type):
build_graph = self.context.build_graph
address = Address.parse(spec)
build_graph.inject_address_closure(address)
target = build_graph.get_target(address)
if not isinstance(target, target_type):
raise self.BadDependency(
'{} must point to a {} target: (was: {})'.format(spec, target_type, target),
)
return target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment