forked from pantsbuild/pants
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is pants implementing `npm install` for node projects modeled in pants. The driving concern is primary control by pants of Node.js packages via BUILD files, and this necessitates generating package.json files under `.pants.d/` to describe dependencies (and later more) to `npm` so it can resolve both local and remote dependencies. A second fallout is copying pants controlled node module sourcecode up into `.pants.d/` since `node` has a documented behavior of resolving against `realpath`[1], foiling any attempt to use symlinks. The basic targets needed to support `resolve.npm` - aka `npm install` are added along with basic tests for the functionality. [1] https://nodejs.org/api/modules.html#modules_addenda_package_manager_tips Testing Done: I manually confirmed that with the NpmResolve execution of `npm dedupe` commented out, the dedupe portion of the `NpmResolveTest.test_resolve_simple_graph` test failed like so: ```console E AssertionError: Expected to find exactly 1 de-duped `typ` package, but found these: E node_modules/typ/package.json E node_modules/util/node_modules/typ/package.json ``` CI went green here: https://travis-ci.org/pantsbuild/pants/builds/77852199 Bugs closed: 2073, 2087 Reviewed at https://rbcommons.com/s/twitter/r/2723/
- Loading branch information
Showing
22 changed files
with
776 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
# TODO(John Sirois): Tie this into the contrib/release_packages.sh the minute the plugin can do | ||
# one useful thing, for example `./pants test contrib/node/examples::`. | ||
contrib_plugin( | ||
name='plugin', | ||
dependencies=[ | ||
'contrib/node/src/python/pants/contrib/node/targets', | ||
'contrib/node/src/python/pants/contrib/node/tasks', | ||
'src/python/pants/base:build_file_aliases', | ||
'src/python/pants/goal:task_registrar', | ||
], | ||
distribution_name='pantsbuild.pants.contrib.node', | ||
description='Node.js support for pants.', | ||
build_file_aliases=True, | ||
register_goals=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# 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.build_file_aliases import BuildFileAliases | ||
from pants.goal.task_registrar import TaskRegistrar as task | ||
|
||
from pants.contrib.node.targets.node_module import NodeModule | ||
from pants.contrib.node.targets.node_remote_module import NodeRemoteModule | ||
from pants.contrib.node.tasks.npm_resolve import NpmResolve | ||
|
||
|
||
def build_file_aliases(): | ||
return BuildFileAliases.create( | ||
targets={ | ||
'node_module': NodeModule, | ||
'node_remote_module': NodeRemoteModule, | ||
}, | ||
) | ||
|
||
|
||
def register_goals(): | ||
task(name='npm', action=NpmResolve).install('resolve') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
target( | ||
name='targets', | ||
dependencies=[ | ||
':node_module', | ||
':node_remote_module', | ||
] | ||
) | ||
|
||
python_library( | ||
name='node_module', | ||
sources=['node_module.py'], | ||
dependencies=[ | ||
':npm_package', | ||
'src/python/pants/base:payload', | ||
], | ||
) | ||
|
||
python_library( | ||
name='node_remote_module', | ||
sources=['node_remote_module.py'], | ||
dependencies=[ | ||
':npm_package', | ||
'src/python/pants/base:payload', | ||
'src/python/pants/base:payload_field', | ||
], | ||
) | ||
|
||
python_library( | ||
name='npm_package', | ||
sources=['npm_package.py'], | ||
dependencies=[ | ||
'src/python/pants/base:payload', | ||
'src/python/pants/base:payload_field', | ||
'src/python/pants/base:target', | ||
] | ||
) |
Empty file.
41 changes: 41 additions & 0 deletions
41
contrib/node/src/python/pants/contrib/node/targets/node_module.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# 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.contrib.node.targets.npm_package import NpmPackage | ||
|
||
|
||
class NodeModule(NpmPackage): | ||
"""Represents a Node module.""" | ||
|
||
def __init__(self, | ||
sources=None, | ||
sources_rel_path=None, | ||
address=None, | ||
payload=None, | ||
**kwargs): | ||
""" | ||
:param sources: Javascript and other source code files that make up this module; paths are | ||
relative to the BUILD file's directory. | ||
:type sources: `globs` , `rglobs` or a list of strings | ||
""" | ||
# TODO(John Sirois): Support devDependencies, etc. The devDependencies case is not | ||
# clear-cut since pants controlled builds would provide devDependencies as needed to perform | ||
# tasks. The reality is likely to be though that both pants will never cover all cases, and a | ||
# back door to execute new tools during development will be desirable and supporting conversion | ||
# of pre-existing package.json files as node_module targets will require this. | ||
|
||
if sources_rel_path is None: | ||
sources_rel_path = address.spec_path | ||
payload = payload or Payload() | ||
payload.add_fields({ | ||
'sources': self.create_sources_field(sources=sources, | ||
sources_rel_path=sources_rel_path, | ||
key_arg='sources'), | ||
}) | ||
super(NodeModule, self).__init__(address=address, payload=payload, **kwargs) |
35 changes: 35 additions & 0 deletions
35
contrib/node/src/python/pants/contrib/node/targets/node_remote_module.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.contrib.node.targets.npm_package import NpmPackage | ||
|
||
|
||
class NodeRemoteModule(NpmPackage): | ||
"""Represents a remote Node module.""" | ||
|
||
def __init__(self, version=None, address=None, payload=None, **kwargs): | ||
""" | ||
:param string version: The version constraint for the remote node module. Any of the forms | ||
accepted by npm including '' or '*' for unconstrained (the default) are | ||
acceptable. See: https://docs.npmjs.com/files/package.json#dependencies | ||
""" | ||
payload = payload or Payload() | ||
payload.add_fields({ | ||
'version': PrimitiveField(version or '*'), # Guard against/allow `None`. | ||
}) | ||
super(NodeRemoteModule, self).__init__(address=address, payload=payload, **kwargs) | ||
|
||
@property | ||
def version(self): | ||
"""The version constraint of the remote package. | ||
:rtype: string | ||
""" | ||
return self.payload.version |
33 changes: 33 additions & 0 deletions
33
contrib/node/src/python/pants/contrib/node/targets/npm_package.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.base.target import Target | ||
|
||
|
||
class NpmPackage(Target): | ||
"""Represents an NPM package.""" | ||
|
||
def __init__(self, package_name=None, address=None, payload=None, **kwargs): | ||
""" | ||
:param string package_name: The remote module package name, if not supplied the target name is | ||
used. | ||
""" | ||
payload = payload or Payload() | ||
payload.add_fields({ | ||
'package_name': PrimitiveField(package_name or address.target_name), | ||
}) | ||
super(NpmPackage, self).__init__(address=address, payload=payload, **kwargs) | ||
|
||
@property | ||
def package_name(self): | ||
"""The name of the remote module package. | ||
:rtype: string | ||
""" | ||
return self.payload.package_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
target( | ||
name='tasks', | ||
dependencies=[ | ||
':npm_resolve', | ||
] | ||
) | ||
|
||
python_library( | ||
name='node_task', | ||
sources=['node_task.py'], | ||
dependencies=[ | ||
'contrib/node/src/python/pants/contrib/node/subsystems:node_distribution', | ||
'contrib/node/src/python/pants/contrib/node/targets:node_module', | ||
'contrib/node/src/python/pants/contrib/node/targets:node_remote_module', | ||
'src/python/pants/backend/core/tasks:task', | ||
'src/python/pants/base:workunit', | ||
'src/python/pants/util:memo', | ||
] | ||
) | ||
|
||
python_library( | ||
name='npm_resolve', | ||
sources=['npm_resolve.py'], | ||
dependencies=[ | ||
':node_task', | ||
'src/python/pants/base:build_environment', | ||
'src/python/pants/base:exceptions', | ||
'src/python/pants/base:workunit', | ||
'src/python/pants/util:contextutil', | ||
'src/python/pants/util:dirutil', | ||
] | ||
) |
Empty file.
83 changes: 83 additions & 0 deletions
83
contrib/node/src/python/pants/contrib/node/tasks/node_task.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# 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.backend.core.tasks.task import Task | ||
from pants.base.workunit import WorkUnit, WorkUnitLabel | ||
from pants.util.memo import memoized_property | ||
|
||
from pants.contrib.node.subsystems.node_distribution import NodeDistribution | ||
from pants.contrib.node.targets.node_module import NodeModule | ||
from pants.contrib.node.targets.node_remote_module import NodeRemoteModule | ||
|
||
|
||
class NodeTask(Task): | ||
|
||
@classmethod | ||
def subsystem_dependencies(cls): | ||
return (NodeDistribution.Factory,) | ||
|
||
@memoized_property | ||
def node_distribution(self): | ||
"""A bootstrapped node distribution for use by node tasks.""" | ||
return NodeDistribution.Factory.global_instance().create() | ||
|
||
@classmethod | ||
def is_node_module(cls, target): | ||
"""Returns `True` if the given target is a `NodeModule`.""" | ||
return isinstance(target, NodeModule) | ||
|
||
@classmethod | ||
def is_node_remote_module(cls, target): | ||
"""Returns `True` if the given target is a `NodeRemoteModule`.""" | ||
return isinstance(target, NodeRemoteModule) | ||
|
||
def execute_node(self, args, workunit_name=None, workunit_labels=None, **kwargs): | ||
"""Executes node passing the given args. | ||
:param list args: The command line args to pass to `node`. | ||
:param string workunit_name: A name for the execution's work unit; defaults to 'node'. | ||
:param list workunit_labels: Any extra :class:`pants.base.workunit.WorkUnitLabel`s to apply. | ||
:param **kwargs: Any extra args to pass to :class:`subprocess.Popen`. | ||
:returns: A tuple of (returncode, command). | ||
:rtype: A tuple of (int, | ||
:class:`pants.contrib.node.subsystems.node_distribution.NodeDistribution.Command`) | ||
""" | ||
npm_command = self.node_distribution.node_command(args=args) | ||
return self._execute_command(npm_command, | ||
workunit_name=workunit_name, | ||
workunit_labels=workunit_labels, | ||
**kwargs) | ||
|
||
def execute_npm(self, args, workunit_name=None, workunit_labels=None, **kwargs): | ||
"""Executes npm passing the given args. | ||
:param list args: The command line args to pass to `npm`. | ||
:param string workunit_name: A name for the execution's work unit; defaults to 'npm'. | ||
:param list workunit_labels: Any extra :class:`pants.base.workunit.WorkUnitLabel`s to apply. | ||
:param **kwargs: Any extra args to pass to :class:`subprocess.Popen`. | ||
:returns: A tuple of (returncode, command). | ||
:rtype: A tuple of (int, | ||
:class:`pants.contrib.node.subsystems.node_distribution.NodeDistribution.Command`) | ||
""" | ||
|
||
npm_command = self.node_distribution.npm_command(args=args) | ||
return self._execute_command(npm_command, | ||
workunit_name=workunit_name, | ||
workunit_labels=workunit_labels, | ||
**kwargs) | ||
|
||
def _execute_command(self, command, workunit_name=None, workunit_labels=None, **kwargs): | ||
workunit_name = workunit_name or command.executable | ||
workunit_labels = {WorkUnitLabel.TOOL} | set(workunit_labels or ()) | ||
with self.context.new_workunit(name=workunit_name, | ||
labels=workunit_labels, | ||
cmd=str(command)) as workunit: | ||
process = command.run(stdout=workunit.output('stdout'), stderr=workunit.output('stderr'), | ||
**kwargs) | ||
returncode = process.wait() | ||
workunit.set_outcome(WorkUnit.SUCCESS if returncode == 0 else WorkUnit.FAILURE) | ||
return returncode, command |
Oops, something went wrong.