Skip to content
This repository has been archived by the owner on Dec 10, 2020. It is now read-only.

Commit

Permalink
Minimal Haskell plugin for pants
Browse files Browse the repository at this point in the history
This is a minimum viable subset of a larger plugin that I have been working on
here:

pantsbuild/pants@master...Gabriel439:ggonzalez/haskell-plugin

The easiest way to review this is to read the `contrib/haskell/README.md` that
this is included within this change.  You can also view the rendered `README.md`
at:

https://github.com/Gabriel439/pants/blob/ggonzalez/haskell-plugin/contrib/haskell/README.md

Note that the `README.md` describes the full set of changes that I have already
made on my larger branch, not just the changes within this review board, but
it's probably better that way since you'll have more context for reviewing this
smaller change.

This change provides:

* New `pants` targets for Haskell projects
* Support for `./pants compile` on these targets
* Example targets

I wasn't sure whether or not to include the integration tests since @jsirois
asked me to keep this small.  I can expand this review board further to add one
of the integration tests I already wrote if people would like to see that, too.

Testing Done:
```
$ ./pants compile contrib/haskell/examples/3rdparty:headless-project
$ ./pants compile contrib/haskell/examples/3rdparty:stack-project
$ ./pants test contrib/haskell/tests/python/pants_test/contrib/haskell/subsystems
```

Link to travis build: https://travis-ci.org/pantsbuild/pants/builds/92311084

Bugs closed: 2377

Reviewed at https://rbcommons.com/s/twitter/r/2975/
  • Loading branch information
Gabriella439 authored and stuhood committed Nov 24, 2015
1 parent b6150c7 commit c1b3b14
Show file tree
Hide file tree
Showing 17 changed files with 1,189 additions and 9 deletions.
715 changes: 715 additions & 0 deletions contrib/haskell/README.md

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions contrib/haskell/examples/3rdparty/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
haskell_stackage_package(
name='pipes',
)

haskell_stackage_package(
name='mmorph',
)

haskell_hackage_package(
name='promises',
version='0.2',
)

haskell_hackage_package(
name='discrimination',
version='0.1',
dependencies=[
':promises'
],
)

haskell_project(
name='headless-project',
resolver='lts-3.1',
dependencies=[
':discrimination',
':mmorph',
':pipes',
],
)

haskell_source_package(
name='stack',
path='https://github.com/commercialhaskell/stack/archive/v0.1.3.1.tar.gz',
)

haskell_project(
name='stack-project',
resolver='lts-3.1',
dependencies=[
':stack',
]
)
16 changes: 16 additions & 0 deletions contrib/haskell/src/python/pants/contrib/haskell/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

contrib_plugin(
name='plugin',
dependencies=[
'contrib/haskell/src/python/pants/contrib/haskell/tasks:tasks',
'contrib/haskell/src/python/pants/contrib/haskell/targets:targets',
'src/python/pants/build_graph',
'src/python/pants/goal:task_registrar',
],
distribution_name='pantsbuild.pants.contrib.haskell',
description='Haskell pants plugin',
build_file_aliases=True,
register_goals=True,
)
30 changes: 30 additions & 0 deletions contrib/haskell/src/python/pants/contrib/haskell/register.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.build_graph.build_file_aliases import BuildFileAliases
from pants.goal.task_registrar import TaskRegistrar

from pants.contrib.haskell.targets.haskell_hackage_package import HaskellHackagePackage
from pants.contrib.haskell.targets.haskell_project import HaskellProject
from pants.contrib.haskell.targets.haskell_source_package import HaskellSourcePackage
from pants.contrib.haskell.targets.haskell_stackage_package import HaskellStackagePackage
from pants.contrib.haskell.tasks.stack_build import StackBuild


def build_file_aliases():
return BuildFileAliases(
targets={
'haskell_hackage_package': HaskellHackagePackage,
'haskell_stackage_package': HaskellStackagePackage,
'haskell_source_package': HaskellSourcePackage,
'haskell_project': HaskellProject,
}
)


def register_goals():
TaskRegistrar(name='stack-build', action=StackBuild).install('compile')
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,23 @@ class StackCommand(namedtuple('StackCommand', ['cmdline', 'env'])):
"""Encapsulates a stack command that can be executed."""

@classmethod
def _create(cls, base_dir, cmd, args=None):
def _create(cls, base_dir, cmd, stack_args=None, cmd_args=None):
# TODO(John Sirois): Right now we take full control of stack flags and only allow the caller
# to pass sub-command args. Consider opening this up as the need arises.

stack_exe = os.path.join(base_dir, 'stack')
cmdline = [stack_exe]
# Ensure we always run with a hermetic ghc
cmdline.extend(['--no-system-ghc', '--install-ghc'])
cmdline.extend([
'--no-system-ghc',
'--install-ghc',
])

if stack_args:
cmdline.extend(stack_args)
cmdline.append(cmd)
if args:
cmdline.extend(args)
if cmd_args:
cmdline.extend(cmd_args)

# We isolate our stack root from the default (~/.stack) using STACK_ROOT.
# See: https://github.com/commercialhaskell/stack/issues/1178
Expand Down Expand Up @@ -127,17 +132,21 @@ def __str__(self):
' ' +
' '.join(self.cmdline))

def create_stack_cmd(self, cmd, args=None):
def create_stack_cmd(self, cmd, stack_args=None, cmd_args=None):
"""Creates a stack command that can be executed later.
:param string cmd: The stack command to execute, e.g. 'setup' for `stack setup`
:param list args: An optional list of arguments and flags to pass to the stack command.
:returns: A stack command that can be executed later.
:rtype: :class:`StackDistribution.StackCommand`
"""
return self.StackCommand._create(self.base_dir, cmd, args=args)
return self.StackCommand._create(
self.base_dir,
cmd,
stack_args=stack_args,
cmd_args=cmd_args)

def execute_stack_cmd(self, cmd, args=None,
def execute_stack_cmd(self, cmd, stack_args=None, cmd_args=None,
workunit_factory=None, workunit_name=None, workunit_labels=None, **kwargs):
"""Runs a stack command.
Expand All @@ -152,7 +161,11 @@ def execute_stack_cmd(self, cmd, args=None,
:returns: A tuple of the exit code and the stack command that was run.
:rtype: (int, :class:`StackDistribution.StackCommand`)
"""
stack_cmd = self.StackCommand._create(self.base_dir, cmd, args=args)
stack_cmd = self.StackCommand._create(
self.base_dir,
cmd,
stack_args=stack_args,
cmd_args=cmd_args)
if workunit_factory is None:
return stack_cmd.spawn(**kwargs).wait()
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
python_library(
name='targets',
sources=globs('*.py'),
dependencies=[
'src/python/pants/build_graph',
]
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.build_graph.target import Target


class HaskellHackagePackage(Target):
"""A package hosted on Hackage.
Only use this target for packages or package versions outside of Stackage.
Prefer `HaskellStackagePackage` when possible.
"""

def __init__(self, version, package=None, **kwargs):
"""
:param str version: The package version string (i.e. "0.4.3.0" or "1.0.0")
:param str package: Optional name of the package (i.e. "network" or "containers"). Defaults to `name` if omitted
"""
self.version = version
self.package = package or kwargs['name']

payload = Payload()
payload.add_fields({
'version': PrimitiveField(self.version),
'package': PrimitiveField(self.package),
})
super(HaskellHackagePackage, self).__init__(**kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.build_graph.target import Target


class HaskellProject(Target):
def __init__(self, resolver, **kwargs):
"""
:param str resolver: The `stack` resolver (i.e. "lts-3.1" or "nightly-2015-08-29")
"""

self.resolver = resolver

payload = Payload()
payload.add_fields({
'resolver': PrimitiveField(self.resolver),
})
super(HaskellProject, self).__init__(payload = payload, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.build_graph.target import Target


class HaskellSourcePackage(Target):
"""A local or remote Haskell source package.
If you provide the `path` field then this target points to the source archive
located at `path`. Otherwise, this target points to a `cabal` source tree anchored
at the current directory.
"""

def __init__(self, package=None, path=None, **kwargs):
"""
:param str package: Optional name of the package (i.e. "network" or "containers"). Defaults to `name` if omitted
:param str path: Optional path to a remote source archive in TAR or ZIP format.
"""

self.package = package or kwargs['name']
self.path = path

payload = Payload()
payload.add_fields({
'package': PrimitiveField(self.package),
'path': PrimitiveField(self.path),
})
super(HaskellSourcePackage, self).__init__(payload = payload, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.build_graph.target import Target


class HaskellStackagePackage(Target):
"""A package hosted on Stackage."""

def __init__(self, package=None, **kwargs):
"""
:param str package: Optional name of the package (i.e. "network" or "containers"). Defaults to `name` if omitted
"""

self.package = package or kwargs['name']

payload = Payload()
payload.add_fields({
'package': PrimitiveField(self.package),
})
super(HaskellStackagePackage, self).__init__(payload = payload, **kwargs)
16 changes: 16 additions & 0 deletions contrib/haskell/src/python/pants/contrib/haskell/tasks/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_library(
name='tasks',
sources=[
'stack_build.py',
'stack_task.py',
],
dependencies=[
'contrib/haskell/src/python/pants/contrib/haskell/subsystems',
'contrib/haskell/src/python/pants/contrib/haskell/targets',
'src/python/pants/backend/core/tasks:common',
],
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding=utf-8
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.contrib.haskell.tasks.stack_task import StackTask


class StackBuild(StackTask):
"""Build the given Haskell targets."""

@classmethod
def register_options(cls, register):
super(StackBuild, cls).register_options(register)
register('--watch',
action='store_true',
help='Watch for changes in local files and automatically rebuild.')

def execute(self):
if self.get_options().watch:
extra_args = ['--file-watch']
else:
extra_args = []
for target in self.context.target_roots:
with self.invalidated(targets=target.closure()) as invalidated:
for vt in invalidated.invalid_vts:
if self.is_haskell_project(vt.target):
self.stack_task('build', vt, extra_args)
Loading

0 comments on commit c1b3b14

Please sign in to comment.