Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
compare: e28fa0de65
  • 2 commits
  • 8 files changed
  • 0 commit comments
  • 1 contributor
View
84 anvil/commands/depends_command.py
@@ -0,0 +1,84 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""Scans all reachable rules for dependencies and installs them.
+Given a set of target rules, all reachable rules will be scanned for
+dependencies that they require to function (such as external Python libraries,
+system tools/libraries/etc).
+
+If the --install option is passed to the command it will attempt to install or
+update all of the discovered dependencies. The command must be run as root
+(via sudo) in order for this to work. For dependencies that install locally
+(such as Node.js modules) they will be placed in the current working directory.
+
+TODO(benvanik): it'd be nice to support * syntax or some way to say 'everything'
+
+Example:
+# Check dependencies and print results for rule :some_rule
+manage.py depends :some_rule
+# Install/update all dependencies for rule :some_rule
+manage.py depends --install :some_rule
+"""
+
+__author__ = 'benvanik@google.com (Ben Vanik)'
+
+
+import argparse
+import os
+import sys
+
+import anvil.commands.util as commandutil
+from anvil.depends import DependencyManager
+from anvil.manage import manage_command
+
+
+def _get_options_parser():
+ """Gets an options parser for the given args."""
+ parser = commandutil.create_argument_parser('manage.py depends', __doc__)
+
+ # Add all common args
+
+ # 'depends' specific
+ parser.add_argument('-i', '--install',
+ dest='install',
+ action='store_true',
+ default=False,
+ help=('Install any missing dependencies. Must be run as '
+ 'root.'))
+ parser.add_argument('--stop_on_error',
+ dest='stop_on_error',
+ action='store_true',
+ default=False,
+ help=('Stop installing when an error is encountered.'))
+ parser.add_argument('targets',
+ nargs='+',
+ metavar='target',
+ help='Target build rule (such as :a or foo/bar:a)')
+
+ return parser
+
+
+@manage_command('depends', 'Manages external rule dependencies.')
+def depends(args, cwd):
+ parser = _get_options_parser()
+ parsed_args = parser.parse_args(args)
+
+ dep_manager = DependencyManager(cwd=cwd)
+ dependencies = dep_manager.scan_dependencies(parsed_args.targets)
+
+ if not len(requirements):
+ print 'No requirements found'
+ return True
+
+ if not parsed_args.install:
+ # TODO(benvanik): prettier output
+ for dependency in dependencies:
+ print dependency
+ return True
+
+ # TODO(benvanik): check if running as root
+ running_as_root = False
+ if parsed_args.install and not running_as_root:
+ print 'Not running as root - run again with sudo'
+ return False
+
+ return dep_manager.install_all(dependencies)
View
2  anvil/commands/util.py
@@ -31,7 +31,7 @@ def create_argument_parser(program_usage, description=''):
Returns:
An ArgumentParser that can be used to parse arguments.
"""
- parser = argparse.ArgumentParser(prog='manage.py serve',
+ parser = argparse.ArgumentParser(prog=program_usage,
description=description,
formatter_class=_ComboHelpFormatter)
_add_common_args(parser)
View
212 anvil/depends.py
@@ -0,0 +1,212 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""Dependency definition and management utilities.
+Rules can define system dependencies such as libraries or applications that
+are required to run them. The build system can then use this metadata to alert
+the user to missing dependencies or help install them.
+"""
+
+# TODO(benvanik): refactor to allow install(deps) batches
+# TODO(benvanik): refactor to make requirements tuples like:
+# ('node-module', 'glsl-unit@1.0.0')
+# ('python-package', 'argparse>=1.0')
+# (['apt-get', 'port'], 'foo')
+# ('brew', 'foo_lib', '1.0')
+
+__author__ = 'benvanik@google.com (Ben Vanik)'
+
+
+import pip
+import pkg_resources
+import os
+import subprocess
+import sys
+
+from anvil.context import BuildEnvironment, BuildContext
+from anvil.project import FileModuleResolver, Project
+from anvil.task import InProcessTaskExecutor
+
+class Dependency(object):
+ """A dependency definition of an external library or application.
+ Dependency definitions contain enough metadata for the build system to display
+ meaningful and actionable error messages in the event of a missing dependency,
+ as well as provide automatic installation support.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initializes a dependency definition.
+ """
+ self.requires_root = False
+
+ def check(self):
+ """Checks to see if the dependency is met.
+
+ Returns:
+ True if the dependency is valid and up to date. If the check could not be
+ performed then None will be returned, signaling that an install is likely
+ required.
+ """
+ raise NotImplementedError()
+
+ def install(self):
+ """Installs the dependency if it is not present.
+
+ Returns:
+ True if the installation completed successfully.
+ """
+ raise NotImplementedError()
+
+
+class NodeLibrary(Dependency):
+ """A dependency on a Node.js library.
+ This will attempt to use npm to install the library locally.
+ """
+
+ def __init__(self, package_str, *args, **kwargs):
+ """Initializes a Node.js library dependency definition.
+
+ Args:
+ package_str: Package string, such as 'some-lib@1.0' or a URL.
+ This is passed directly to npm.
+ """
+ super(NodeLibrary, self).__init__(*args, **kwargs)
+ self.package_str = package_str
+
+ def check(self):
+ # TODO(benvanik): find a way to check with NPM?
+ # Could invoke node -e 'require("%s")'? would need the name to use
+ return None
+
+ def install(self):
+ return subprocess.call(['npm', 'install', self.package_str]) == 0
+
+
+class PythonLibrary(Dependency):
+ """A dependency on a Python library.
+ This uses pip to query the available libraries and install new ones.
+ """
+
+ def __init__(self, requirement_str, *args, **kwargs):
+ """Initializes a Python library dependency definition.
+
+ Args:
+ requirement_str: Requirement string, such as 'anvil-build>=0.0.1'.
+ This is passed directly to pip, so it supports extras and other
+ features of requirement strings.
+ """
+ super(PythonLibrary, self).__init__(*args, **kwargs)
+ self.requires_root = True
+ self.requirement_str = requirement_str
+ self.requirement = pkg_resources.Requirement.parse(requirement_str)
+
+ def __str__(self):
+ return 'Python Library "%s"' % (self.requirement_str)
+
+ def check(self):
+ any_found = False
+ any_valid = False
+ for distro in pip.get_installed_distributions():
+ # distro is a pkg_resources.Distribution
+ if distro in self.requirement:
+ # Found and valid!
+ any_found = True
+ any_valid = True
+ elif distro.project_name == self.requirement.project_name:
+ # Present, but old
+ any_found = True
+ any_valid = False
+ # TODO(benvanik): something with the result? log? different values?
+ return any_found and any_valid
+
+ def install(self):
+ return pip.main(['install', self.requirement_str]) == 0
+
+
+class NativePackage(Dependency):
+ """A dependency on a native system package.
+ This will attempt to use a supported platform package manager such as MacPorts
+ or apt-get to install a dependency. If that fails it can (if supported) try
+ to build from source.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initializes a native system dependency definition.
+
+ Args:
+ ??
+ """
+ super(NativePackage, self).__init__(*args, **kwargs)
+ self.requires_root = True
+
+ def check(self):
+ return None
+
+ def install(self):
+ return False
+
+ def _get_package_manager(self):
+ # TODO(benvanik): switch _PackageManager type based on platform? detect?
+ return None
+
+ class _PackageManager(object):
+ pass
+
+ class _AptGetPackageManager(_PackageManager):
+ pass
+
+ class _MacPortsPackageManager(_PackageManager):
+ pass
+
+ class _HomebrewPackageManager(_PackageManager):
+ pass
+
+
+class DependencyManager(object):
+ """
+ """
+
+ def __init__(self, cwd=None, *args, **kwargs):
+ """
+ Args:
+ cwd: Current working directory.
+ """
+ self.cwd = cwd if cwd else os.getcwd()
+
+ def scan_dependencies(self, target_rule_names):
+ """Scans a list of target rules for their dependency information.
+
+ Args:
+ target_rule_names: A list of rule names that are to be executed.
+
+ Returns:
+ A de-duplicated list of Dependency definitions.
+ """
+ build_env = BuildEnvironment(root_path=self.cwd)
+ module_resolver = FileModuleResolver(self.cwd)
+ project = Project(module_resolver=module_resolver)
+ dependencies = []
+ with BuildContext(build_env, project,
+ task_executor=InProcessTaskExecutor(),
+ stop_on_error=False,
+ raise_on_error=False) as build_ctx:
+ rule_sequence = build_ctx.rule_graph.calculate_rule_sequence(
+ target_rule_names)
+ for rule in rule_sequence:
+ if hasattr(rule, 'requires'):
+ dependencies.extend(rule.requires)
+ # TODO(benvanik): de-duplicate
+ return dependencies
+
+ def install_all(self, dependencies):
+ """Installs all of the given dependencies.
+
+ Args:
+ dependencies: A list of Dependency definitions to install.
+
+ Returns:
+ True if the installs succeeded.
+ """
+ # TODO(benvanik): sort by type first so batch install can be used
+ raise NotImplementedError()
+
+dependencies = []
View
51 anvil/depends_test.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""Tests for the depends module.
+"""
+
+__author__ = 'benvanik@google.com (Ben Vanik)'
+
+
+import os
+import unittest2
+
+from depends import *
+
+
+class DependencyTest(unittest2.TestCase):
+ """Behavioral tests of the Dependency type."""
+
+ def testNodeLibrary(self):
+ # TODO(benvanik): test NodeLibrary
+ NodeLibrary('glsl-unit')
+ pass
+
+ def testPythonLibrary(self):
+ # TODO(benvanik): test PythonLibrary
+ PythonLibrary('argparse')
+ pass
+
+ def testNativePackage(self):
+ # TODO(benvanik): test NativePackage
+ NativePackage()
+ pass
+
+
+class DependencyManagerTest(unittest2.TestCase):
+ """Behavioral tests of the DependencyManager type."""
+
+ def testScanDependencies(self):
+ # TODO(benvanik): test scan_dependencies
+ DependencyManager()
+ pass
+
+ def testInstallAll(self):
+ # TODO(benvanik): test install_all
+ DependencyManager()
+ pass
+
+
+if __name__ == '__main__':
+ unittest2.main()
View
19 anvil/manage.py
@@ -16,15 +16,6 @@
import util
-def _get_anvil_path():
- """Gets the anvil/ path.
-
- Returns:
- The full path to the anvil/ source.
- """
- return os.path.normpath(os.path.dirname(__file__))
-
-
def manage_command(command_name, command_help=None):
"""A decorator for management command functions.
Use this to register management command functions. A function decorated with
@@ -60,7 +51,7 @@ def discover_commands(search_path=None):
"""
commands = {}
if not search_path:
- commands_path = os.path.join(_get_anvil_path(), 'commands')
+ commands_path = os.path.join(util.get_anvil_path(), 'commands')
else:
commands_path = search_path
for (root, dirs, files) in os.walk(commands_path):
@@ -90,7 +81,9 @@ def usage(commands):
s = 'manage.py command [-h]\n'
s += '\n'
s += 'Commands:\n'
- for command_name in commands:
+ command_names = commands.keys()
+ command_names.sort()
+ for command_name in command_names:
s += ' %s\n' % (command_name)
command_help = commands[command_name].command_help
if command_help:
@@ -136,7 +129,7 @@ def run_command(args=None, cwd=None, commands=None):
def main(): # pragma: no cover
"""Entry point for scripts."""
# Always add anvil/.. to the path
- sys.path.insert(1, _get_anvil_path())
+ sys.path.insert(1, util.get_anvil_path())
commands = discover_commands()
@@ -155,4 +148,6 @@ def main(): # pragma: no cover
if __name__ == '__main__':
+ # Always add anvil/.. to the path
+ sys.path.insert(1, os.path.join(util.get_anvil_path(), '..'))
main()
View
2  anvil/test.py
@@ -120,7 +120,7 @@ def setUp(self):
# Copy fixture files
if self.fixture:
self.root_path = os.path.join(self.root_path, self.fixture)
- build_path = util.find_build_path()
+ build_path = util.get_anvil_path()
if not build_path:
raise Error('Unable to find build path')
fixture_path = os.path.join(
View
14 anvil/util.py
@@ -17,19 +17,13 @@
timer = time.time # pragma: no cover
-def find_build_path(): # pragma: no cover
- """Scans up the current path for the anvil/ folder.
+def get_anvil_path():
+ """Gets the anvil/ path.
Returns:
- The 'anvil/' folder.
+ The full path to the anvil/ source.
"""
- path = sys.path[0]
- while True:
- if os.path.exists(os.path.join(path, 'anvil')):
- return os.path.join(path, 'anvil')
- path = os.path.dirname(path)
- if not len(path):
- return None
+ return os.path.normpath(os.path.dirname(__file__))
def is_rule_path(value):
View
1  setup.py
@@ -45,6 +45,7 @@
'autobahn>=0.5.1',
'glob2>=0.3',
'networkx>=1.6',
+ 'pip>=1.1',
'Sphinx>=1.1.3',
'watchdog>=0.6',
]

No commit comments for this range

Something went wrong with that request. Please try again.