# ==================================================================================================
# Copyright 2011 Twitter, Inc.
# --------------------------------------------------------------------------------------------------
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this work except in compliance with the License.
# You may obtain a copy of the License in the LICENSE file, or at:
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==================================================================================================

from __future__ import print_function

import inspect
import os
import sys
import traceback

from contextlib import contextmanager
from optparse import Option, OptionParser

try:
  from colors import cyan, green, red, yellow
except ImportError:
  turn_off_colored_logging = True
else:
  turn_off_colored_logging = False

from functools import wraps

from twitter.common import log
from twitter.common.log.options import LogOptions
from twitter.common.collections import OrderedSet
from twitter.common.dirutil import safe_mkdir, safe_rmtree
from twitter.common.lang import Compatibility
from twitter.pants import get_buildroot, goal, group, has_sources, is_apt
from twitter.pants.base import (
    Address,
    BuildFile,
    Config,
    ParseContext,
    Target,
    TargetDefinitionException)
from twitter.pants.base.rcfile import RcFile
from twitter.pants.buildtimestats import BuildTimeStats
from twitter.pants.commands import Command
from twitter.pants.engine import Engine, GroupEngine
from twitter.pants.goal import Context, GoalError, Phase
from twitter.pants.tasks import Task, TaskError
from twitter.pants.tasks.targets_help import TargetsHelp

StringIO = Compatibility.StringIO


class List(Task):
  @classmethod
  def setup_parser(cls, option_group, args, mkflag):
    option_group.add_option(mkflag("all"), dest="goal_list_all", default=False, action="store_true",
                            help="[%default] List all goals even if no description is available.")

  def execute(self, targets):
    print('Installed goals:')
    documented_rows = []
    undocumented = []
    max_width = 0
    for phase, _ in Phase.all():
      if phase.description:
        documented_rows.append((phase.name, phase.description))
        max_width = max(max_width, len(phase.name))
      elif self.context.options.goal_list_all:
        undocumented.append(phase.name)
    for name, description in documented_rows:
      print('  %s: %s' % (name.rjust(max_width), description))
    if undocumented:
      print('\nUndocumented goals:\n  %s' % ' '.join(undocumented))


goal(name='goals', action=List).install().with_description('List all documented goals.')


goal(name='targets', action=TargetsHelp).install().with_description('List all target types.')


class Help(Task):
  @classmethod
  def setup_parser(cls, option_group, args, mkflag):
    default = None
    if len(args) > 1 and (not args[1].startswith('-')):
      default = args[1]
      del args[1]
    option_group.add_option(mkflag("goal"), dest="help_goal", default=default)

  def execute(self, targets):
    goal = self.context.options.help_goal
    if goal is None:
      return self.list_goals('You must supply a goal name to provide help for.')
    phase = Phase(goal)
    if not phase.goals():
      return self.list_goals('Goal %s is unknown.' % goal)

    parser = OptionParser()
    parser.set_usage('%s goal %s ([target]...)' % (sys.argv[0], goal))
    parser.epilog = phase.description
    Goal.add_global_options(parser)
    Phase.setup_parser(parser, [], [phase])
    parser.parse_args(['--help'])

  def list_goals(self, message):
    print(message)
    print()
    return Goal.execute(self.context, 'goals')

goal(name='help', action=Help).install().with_description('Provide help for the specified goal.')


class SpecParser(object):
  """Parses goal target specs; either simple target addresses or else sibling (:) or descendant
  (::) selector forms
  """

  def __init__(self, root_dir):
    self._root_dir = root_dir

  def _get_dir(self, spec):
    path = spec.split(':', 1)[0]
    if os.path.isdir(path):
      return path
    else:
      if os.path.isfile(path):
        return os.path.dirname(path)
      else:
        return spec

  def _parse_addresses(self, spec):
    if spec.endswith('::'):
      dir = self._get_dir(spec[:-len('::')])
      for buildfile in BuildFile.scan_buildfiles(self._root_dir, os.path.join(self._root_dir, dir)):
        for address in Target.get_all_addresses(buildfile):
          yield address
    elif spec.endswith(':'):
      dir = self._get_dir(spec[:-len(':')])
      for address in Target.get_all_addresses(BuildFile(self._root_dir, dir)):
        yield address
    else:
      yield Address.parse(self._root_dir, spec)

  def parse(self, spec):
    """Parses the given target spec into one or more targets.

    Returns a generator of target, address pairs in which the target may be None if the address
    points to a non-existent target.
    """
    for address in self._parse_addresses(spec):
      target = Target.get(address)
      yield target, address


class Goal(Command):
  """Lists installed goals or else executes a named goal."""

  __command__ = 'goal'

  GLOBAL_OPTIONS = [
    Option("-t", "--timeout", dest="conn_timeout", type='int',
           default=Config.load().getdefault('connection_timeout'),
           help="Number of seconds to wait for http connections."),
    Option("-x", "--time", action="store_true", dest="time", default=False,
           help="Times goal phases and outputs a report."),
    Option("-e", "--explain", action="store_true", dest="explain", default=False,
           help="Explain the execution of goals."),
    Option("--force-stats-upload", dest="force_upload", action="store_true", default=False,
           help="[%default] Forces pants runtime stats upload after every pants runs"),
    Option("-v", "--log", action="store_true", dest="log", default=False,
           help="[%default] Logs extra build output."),
    Option("-d", "--logdir", dest="logdir",
           help="[%default] Forks logs to files under this directory."),
    Option("-l", "--level", dest="log_level", type="choice", choices=['debug', 'info', 'warn'],
           help="[info] Sets the logging level to one of 'debug', 'info' or 'warn', implies -v "
                  "if set."),
    Option("--no-colors", dest="no_color", action="store_true", default=turn_off_colored_logging,
           help="Do not colorize log messages."),
    Option("--all", dest="target_directory", action="append",
           help="DEPRECATED: Use [dir]: with no flag in a normal target position on the command "
                  "line. (Adds all targets found in the given directory's BUILD file. Can be "
                  "specified more than once.)"),
    Option("--all-recursive", dest="recursive_directory", action="append",
           help="DEPRECATED: Use [dir]:: with no flag in a normal target position on the command "
                  "line. (Adds all targets found recursively under the given directory. Can be "
                  "specified more than once to add more than one root target directory to scan.)")
  ]

  @staticmethod
  def add_global_options(parser):
    for option in Goal.GLOBAL_OPTIONS:
      parser.add_option(option)

  @staticmethod
  def parse_args(args):
    goals = OrderedSet()
    specs = OrderedSet()
    help = False
    explicit_multi = False

    def is_spec(spec):
      return os.sep in spec or ':' in spec

    for i, arg in enumerate(args):
      help = help or 'help' == arg
      if not arg.startswith('-'):
        specs.add(arg) if is_spec(arg) else goals.add(arg)
      elif '--' == arg:
        if specs:
          raise GoalError('Cannot intermix targets with goals when using --. Targets should '
                          'appear on the right')
        explicit_multi = True
        del args[i]
        break

    if explicit_multi:
      spec_offset = len(goals) + 1 if help else len(goals)
      specs.update(arg for arg in args[spec_offset:] if not arg.startswith('-'))

    return goals, specs

  @staticmethod
  def execute(context, *names):
    parser = OptionParser()
    phases = [Phase(name) for name in names]
    Phase.setup_parser(parser, [], phases)
    options, _ = parser.parse_args([])
    context = Context(context.config, options, context.target_roots, log=context.log)
    return Goal._execute(context, phases, print_timing=False, force_stats_upload=False)

  @staticmethod
  def _execute(context, phases, print_timing, force_stats_upload):
    build_stats = None
    if context.config.getdefault('stats_collection', bool, default=False):
      user = context.config.getdefault('user')
      build_stats = BuildTimeStats(user, force_stats_upload)
    engine = GroupEngine(print_timing=print_timing, build_stats=build_stats)
    return engine.execute(context, phases)

  # TODO(John Sirois): revisit wholesale locking when we move py support into pants new
  @classmethod
  def serialized(cls):
    return True

  def __init__(self, root_dir, parser, args):
    self.targets = []
    Command.__init__(self, root_dir, parser, args)

  @contextmanager
  def check_errors(self, banner):
    errors = {}
    def error(key, include_traceback=False):
      exc_type, exc_value, _ = sys.exc_info()
      msg = StringIO()
      if include_traceback:
        frame = inspect.trace()[-1]
        filename = frame[1]
        lineno = frame[2]
        funcname = frame[3]
        code = ''.join(frame[4])
        traceback.print_list([(filename, lineno, funcname, code)], file=msg)
      if exc_type:
        msg.write(''.join(traceback.format_exception_only(exc_type, exc_value)))
      errors[key] = msg.getvalue()
      sys.exc_clear()

    yield error

    if errors:
      msg = StringIO()
      msg.write(banner)
      invalid_keys = [key for key, exc in errors.items() if not exc]
      if invalid_keys:
        msg.write('\n  %s' % '\n  '.join(invalid_keys))
      for key, exc in errors.items():
        if exc:
          msg.write('\n  %s =>\n    %s' % (key, '\n      '.join(exc.splitlines())))
      self.error(msg.getvalue())

  def setup_parser(self, parser, args):
    self.config = Config.load()

    Goal.add_global_options(parser)

    # We support attempting zero or more goals.  Multiple goals must be delimited from further
    # options and non goal args with a '--'.  The key permutations we need to support:
    # ./pants goal => goals
    # ./pants goal goals => goals
    # ./pants goal compile src/java/... => compile
    # ./pants goal compile -x src/java/... => compile
    # ./pants goal compile src/java/... -x => compile
    # ./pants goal compile run -- src/java/... => compile, run
    # ./pants goal compile run -- src/java/... -x => compile, run
    # ./pants goal compile run -- -x src/java/... => compile, run

    if not args:
      args.append('goals')

    if len(args) == 1 and args[0] in set(['-h', '--help', 'help']):
      def format_usage(usages):
        left_colwidth = 0
        for left, right in usages:
          left_colwidth = max(left_colwidth, len(left))
        lines = []
        for left, right in usages:
          lines.append('  %s%s%s' % (left, ' ' * (left_colwidth - len(left) + 1), right))
        return '\n'.join(lines)

      usages = [
        ("%prog goal goals ([spec]...)", Phase('goals').description),
        ("%prog goal help [goal] ([spec]...)", Phase('help').description),
        ("%prog goal [goal] [spec]...", "Attempt goal against one or more targets."),
        ("%prog goal [goal] ([goal]...) -- [spec]...", "Attempts all the specified goals."),
      ]
      parser.set_usage("\n%s" % format_usage(usages))
      parser.epilog = ("Either lists all installed goals, provides extra help for a goal or else "
                       "attempts to achieve the specified goal for the listed targets." """
                       Note that target specs accept two special forms:
                         [dir]:  to include all targets in the specified directory
                         [dir]:: to include all targets found in all BUILD files recursively under
                                 the directory""")

      parser.print_help()
      sys.exit(0)
    else:
      goals, specs = Goal.parse_args(args)

      # Bootstrap goals by loading any configured bootstrap BUILD files
      with self.check_errors('The following bootstrap_buildfiles cannot be loaded:') as error:
        for path in self.config.getlist('goals', 'bootstrap_buildfiles', default=[]):
          try:
            buildfile = BuildFile(get_buildroot(), os.path.relpath(path, get_buildroot()))
            ParseContext(buildfile).parse()
          except (TypeError, ImportError, TaskError, GoalError):
            error(path, include_traceback=True)
          except (IOError, SyntaxError):
            error(path)

      # Bootstrap user goals by loading any BUILD files implied by targets
      spec_parser = SpecParser(self.root_dir)
      with self.check_errors('The following targets could not be loaded:') as error:
        for spec in specs:
          try:
            for target, address in spec_parser.parse(spec):
              if target:
                self.targets.append(target)
                # Force early BUILD file loading if this target is an alias that expands to others.
                unused = list(target.resolve())
              else:
                siblings = Target.get_all_addresses(address.buildfile)
                prompt = 'did you mean' if len(siblings) == 1 else 'maybe you meant one of these'
                error('%s => %s?:\n    %s' % (address, prompt,
                                              '\n    '.join(str(a) for a in siblings)))
          except (TypeError, ImportError, TaskError, GoalError):
            error(spec, include_traceback=True)
          except (IOError, SyntaxError, TargetDefinitionException):
            error(spec)

      self.phases = [Phase(goal) for goal in goals]

      rcfiles = self.config.getdefault('rcfiles', type=list, default=[])
      if rcfiles:
        rcfile = RcFile(rcfiles, default_prepend=False, process_default=True)

        # Break down the goals specified on the command line to the full set that will be run so we
        # can apply default flags to inner goal nodes.  Also break down goals by Task subclass and
        # register the task class hierarchy fully qualified names so we can apply defaults to
        # baseclasses.

        sections = OrderedSet()
        for phase in Engine.execution_order(self.phases):
          for goal in phase.goals():
            sections.add(goal.name)
            for clazz in goal.task_type.mro():
              if clazz == Task:
                break
              sections.add('%s.%s' % (clazz.__module__, clazz.__name__))

        augmented_args = rcfile.apply_defaults(sections, args)
        if augmented_args != args:
          del args[:]
          args.extend(augmented_args)
          print("(using pantsrc expansion: pants goal %s)" % ' '.join(augmented_args),
                file=sys.stderr)

      Phase.setup_parser(parser, args, self.phases)

  def run(self, lock):
    logger = None
    if self.options.log or self.options.log_level:
      LogOptions.set_stderr_log_level((self.options.log_level or 'info').upper())
      logdir = self.options.logdir or self.config.get('goals', 'logdir', default=None)
      if logdir:
        safe_mkdir(logdir)
        LogOptions.set_log_dir(logdir)
        log.init('goals')
      else:
        log.init()
      logger = log

      if not self.options.no_color:
        def colorwrapper(func, clrname):
          @wraps(func)
          def wrapper(msg, *args, **kwargs):
            return func(clrname(msg), *args, **kwargs)
          return wrapper

        log.info = colorwrapper(log.info, green)
        log.warn = colorwrapper(log.warn, yellow)
        log.error = colorwrapper(log.error, red)
        log.debug = colorwrapper(log.debug, cyan)

    if self.options.recursive_directory:
      log.warn('--all-recursive is deprecated, use a target spec with the form [dir]:: instead')
      for dir in self.options.recursive_directory:
        self.add_target_recursive(dir)

    if self.options.target_directory:
      log.warn('--all is deprecated, use a target spec with the form [dir]: instead')
      for dir in self.options.target_directory:
        self.add_target_directory(dir)

    buildtime_stats = BuildTimeStats(self.config.getdefault("user"),
                                     force_upload=self.options.force_upload)

    context = Context(self.config, self.options, self.targets, lock=lock, log=logger,
                      buildtime_stats=buildtime_stats)

    unknown = []
    for phase in self.phases:
      if not phase.goals():
        unknown.append(phase)

    if unknown:
      print('Unknown goal(s): %s\n' % ' '.join(phase.name for phase in unknown))
      Goal.execute(context, 'goals')
      return 1

    if logger:
      logger.debug('Operating on targets: %s' % self.targets)

    return Goal._execute(context, self.phases, print_timing=self.options.time,
                         force_stats_upload=self.options.force_upload)


# Install all default pants provided goals
from twitter.pants import junit_tests
from twitter.pants.targets import (
  Benchmark,
  JavaLibrary,
  JvmBinary,
  ScalacPlugin,
  ScalaLibrary,
  ScalaTests)
from twitter.pants.tasks.antlr_gen import AntlrGen
from twitter.pants.tasks.benchmark_run import BenchmarkRun
from twitter.pants.tasks.binary_create import BinaryCreate
from twitter.pants.tasks.builddictionary import BuildBuildDictionary
from twitter.pants.tasks.bundle_create import BundleCreate
from twitter.pants.tasks.checkstyle import Checkstyle
from twitter.pants.tasks.check_published_deps import CheckPublishedDeps
from twitter.pants.tasks.detect_duplicates import DuplicateDetector
from twitter.pants.tasks.extract import Extract
from twitter.pants.tasks.filedeps import FileDeps
from twitter.pants.tasks.idl_extract import IdlExtract
from twitter.pants.tasks.idl_resolve import IdlResolve
from twitter.pants.tasks.ivy_resolve import IvyResolve
from twitter.pants.tasks.jar_create import JarCreate
from twitter.pants.tasks.java_compile import JavaCompile
from twitter.pants.tasks.javadoc_gen import JavadocGen
from twitter.pants.tasks.scaladoc_gen import ScaladocGen
from twitter.pants.tasks.junit_run import JUnitRun
from twitter.pants.tasks.jvm_run import JvmRun
from twitter.pants.tasks.markdown_to_html import MarkdownToHtml
from twitter.pants.tasks.nailgun_task import NailgunTask
from twitter.pants.tasks.listtargets import ListTargets
from twitter.pants.tasks.pathdeps import PathDeps
from twitter.pants.tasks.prepare_resources import PrepareResources
from twitter.pants.tasks.protobuf_gen import ProtobufGen
from twitter.pants.tasks.scala_compile import ScalaCompile
from twitter.pants.tasks.scala_repl import ScalaRepl
from twitter.pants.tasks.specs_run import SpecsRun
from twitter.pants.tasks.thrift_gen import ThriftGen
from twitter.pants.tasks.scrooge_gen import ScroogeGen


class Invalidator(Task):
  def execute(self, targets):
    self.invalidate(all_=True)
goal(name='invalidate', action=Invalidator).install().with_description('Invalidate all caches')


goal(
  name='clean-all',
  action=lambda ctx: safe_rmtree(ctx.config.getdefault('pants_workdir')),
  dependencies=['invalidate']
).install().with_description('Cleans all intermediate build output')


if NailgunTask.killall:
  class NailgunKillall(Task):
    @classmethod
    def setup_parser(cls, option_group, args, mkflag):
      option_group.add_option(mkflag("everywhere"), dest="ng_killall_evywhere",
                              default=False, action="store_true",
                              help="[%default] Kill all nailguns servers launched by pants for "
                                   "all workspaces on the system.")

    def execute(self, targets):
      if NailgunTask.killall:
        NailgunTask.killall(logger=self.context.log.info,
                            everywhere=self.context.options.ng_killall_evywhere)

  ng_killall = goal(name='ng-killall', action=NailgunKillall)
  ng_killall.install().with_description('Kill any running nailgun servers spawned by pants.')

  ng_killall.install('clean-all', first=True)


# TODO(John Sirois): Resolve eggs
goal(
  name='ivy',
  action=IvyResolve,
  dependencies=['gen']
).install('resolve').with_description('Resolves jar dependencies and produces dependency reports.')

goal(
  name='idl',
  action=IdlResolve,
).install('resolve-idl').with_description('Resolves idl jar dependencies.')

goal(
  name='extract',
  action=Extract,
).install('resolve-idl')

goal(
  name='idl-extract',
  action=IdlExtract,
).install('resolve-idl')

# TODO(John Sirois): gen attempted as the sole Goal should gen for all known gen types but
# recognize flags to narrow the gen set
goal(name='thrift', action=ThriftGen,
  dependencies=['resolve-idl']).install('gen').with_description('Generate code.')
goal(name='scrooge', action=ScroogeGen,
  dependencies=['resolve-idl']).install('gen')
goal(name='protoc', action=ProtobufGen,
  dependencies=['resolve-idl']).install('gen')
goal(name='antlr', action=AntlrGen,
  dependencies=['resolve-idl']).install('gen')

goal(
  name='checkstyle',
  action=Checkstyle,
  dependencies=['gen', 'resolve']
).install().with_description('Run checkstyle against java source code.')

# TODO(John Sirois): These group predicates could simplify to simple has_sources checks except for
# the fact that sometimes 'aggregator' targets with no sources serve as a dependency link in the
# wild.  Consider stomping this practice out and simplifying these predicates.

def is_java(target):
  return (isinstance(target, JavaLibrary)
          or (isinstance(target, (JvmBinary, junit_tests, Benchmark))
              and has_sources(target, '.java')))


def is_scala(target):
  return (isinstance(target, (ScalaLibrary, ScalaTests, ScalacPlugin))
          or (isinstance(target, (JvmBinary, junit_tests, Benchmark))
              and has_sources(target, '.scala')))


goal(name='scalac',
     action=ScalaCompile,
     group=group('jvm', is_scala),
     dependencies=['gen', 'resolve']).install('compile').with_description(
       'Compile both generated and checked in code.'
     )
goal(name='apt',
     action=JavaCompile,
     group=group('jvm', is_apt),
     dependencies=['gen', 'resolve']).install('compile')
goal(name='javac',
     action=JavaCompile,
     group=group('jvm', is_java),
     dependencies=['gen', 'resolve']).install('compile')


goal(name='prepare', action=PrepareResources).install('resources')


# TODO(John Sirois): pydoc also
goal(name='javadoc',
     action=JavadocGen,
     dependencies=['compile']).install('doc').with_description('Create documentation.')
goal(name='scaladoc',
     action=ScaladocGen,
     dependencies=['compile']).install('doc')


if MarkdownToHtml.AVAILABLE:
  goal(name='markdown',
       action=MarkdownToHtml
  ).install('markdown').with_description('Generate html from markdown docs.')


class ScaladocJarShim(ScaladocGen):
  def __init__(self, context, output_dir=None, confs=None):
    super(ScaladocJarShim, self).__init__(context,
                                          output_dir=output_dir,
                                          confs=confs,
                                          active=False)


class JavadocJarShim(JavadocGen):
  def __init__(self, context, output_dir=None, confs=None):
    super(JavadocJarShim, self).__init__(context,
                                         output_dir=output_dir,
                                         confs=confs,
                                         active=False)


class JarCreateGoal(JarCreate):
  def __init__(self, context):
    super(JarCreateGoal, self).__init__(context, False)

goal(name='javadoc_publish',
     action=JavadocJarShim).install('jar')
goal(name='scaladoc_publish',
     action=ScaladocJarShim).install('jar')
goal(name='jar',
     action=JarCreateGoal,
     dependencies=['compile', 'resources']).install('jar').with_description('Create one or more jars.')
goal(name='check_published_deps',
     action=CheckPublishedDeps
).install('check_published_deps').with_description(
  'Find references to outdated artifacts published from this BUILD tree.')

goal(name='junit',
     action=JUnitRun,
     dependencies=['compile', 'resources']).install('test').with_description('Test compiled code.')
goal(name='specs',
     action=SpecsRun,
     dependencies=['compile', 'resources']).install('test')

goal(name='bench',
     action=BenchmarkRun,
     dependencies=['compile', 'resources']).install('bench')

# TODO(John Sirois): Create pex's in binary phase
goal(
  name='binary',
  action=BinaryCreate,
  dependencies=['jar']
).install().with_description('Create a jvm binary jar.')
goal(
  name='dup',
  action=DuplicateDetector,
).install('binary')
goal(
  name='bundle',
  action=BundleCreate,
  dependencies=['binary']
).install().with_description('Create an application bundle from binary targets.')

goal(
  name='detect-duplicates',
  action=DuplicateDetector,
  dependencies=['jar']
).install().with_description('Detect duplicate classes and resources on the classpath.')

goal(
  name='jvm-run',
  action=JvmRun,
  dependencies=['compile', 'resources']
).install('run').with_description('Run a (currently JVM only) binary target.')

goal(
  name='scala-repl',
  action=ScalaRepl,
  dependencies=['compile', 'resources']
).install('repl').with_description(
  'Run a (currently Scala only) REPL with the classpath set according to the targets.')

goal(
  name='filedeps',
  action=FileDeps
).install('filedeps').with_description('Print out a list of all files the target depends on')

goal(
  name='pathdeps',
  action=PathDeps
).install('pathdeps').with_description(
  'Print out a list of all paths containing build files the target depends on')

goal(
  name='list',
  action=ListTargets
).install('list').with_description('List available BUILD targets.')

goal(  # Generate the BUILD Dictionary
  name='builddict',
  action=BuildBuildDictionary
).install('builddict')

from twitter.pants.tasks.idea_gen import IdeaGen

goal(
  name='idea',
  action=IdeaGen,
  dependencies=['jar']
).install().with_description('Create an IntelliJ IDEA project from the given targets.')


from twitter.pants.tasks.eclipse_gen import EclipseGen

goal(
  name='eclipse',
  action=EclipseGen,
  dependencies=['jar']
).install().with_description('Create an Eclipse project from the given targets.')


from twitter.pants.tasks.python.setup import SetupPythonEnvironment

goal(
  name='python-setup',
  action=SetupPythonEnvironment,
).install('setup').with_description(
"Setup the target's build environment.")


from twitter.pants.tasks.dependees import ReverseDepmap

goal(
  name='dependees',
  action=ReverseDepmap
).install().with_description('Print a reverse dependency mapping for the given targets')


from twitter.pants.tasks.depmap import Depmap

goal(
  name='depmap',
  action=Depmap
).install().with_description('Generates either a textual dependency tree or a graphviz'
                             ' digraph dotfile for the dependency set of a target')


from twitter.pants.tasks.dependencies import Dependencies

goal(
  name='dependencies',
  action=Dependencies
).install().with_description('Extract textual infomation about the dependencies of a target')


from twitter.pants.tasks.filemap import Filemap

goal(
  name='filemap',
  action=Filemap
).install().with_description('Outputs a mapping from source file to'
                             ' the target that owns the source file')


from twitter.pants.tasks.minimal_cover import MinimalCover

goal(
  name='minimize',
  action=MinimalCover
).install().with_description('Print the minimal cover of the given targets.')


from twitter.pants.tasks.filter import Filter

goal(
  name='filter',
  action=Filter
).install().with_description('Filter the input targets based on various criteria.')


from twitter.pants.tasks.sorttargets import SortTargets

goal(
  name='sort',
  action=SortTargets
).install().with_description('Topologically sort the input targets.')


from twitter.pants.tasks.roots import ListRoots

goal(
  name='roots',
  action=ListRoots,
).install('roots').with_description("Prints the source roots and associated target types defined in the repo.")
