class PackageOptions(object):
    @classmethod
    def __from_data(cls, data):
        raw = raw_parse(data)
        kw = raw_to_options_kw(raw)
        return cls(**kw)

    @classmethod
    def from_string(cls, str):
        """Create a PackageOptions instance from a bento.info content."""
        return cls.__from_data(str)

    @classmethod
    def from_file(cls, filename):
        """Create a PackageOptions instance from a bento.info file."""
        fid = open(filename, 'r')
        try:
            data = fid.read()
            return cls.__from_data(data)
        finally:
            fid.close()

    def __init__(self, name, path_options=None, flag_options=None):
        """Create a PackageOptions instance

        Parameters
        ----------
        name: str
            name of the package
        path_options: dict
            dict of path options
        flag_options: dict
            dict of flag options
        """
        self.name = name

        if not path_options:
            self.path_options = {}
        else:
            self.path_options = path_options

        if not flag_options:
            self.flag_options = {}
        else:
            self.flag_options = flag_options def expected_failure(f):
    return unittest.expectedFailure(f) def skip_if(condition, msg=""):
    return unittest.skipIf(condition, msg) def subst_vars (s, local_vars):
    """Perform shell/Perl-style variable substitution.

    Every occurrence of '$' followed by a name is considered a variable, and
    variable is substituted by the value found in the `local_vars' dictionary.
    Raise ValueError for any variables not found in `local_vars'.

    '$' may be escaped by using '$$'

    Parameters
    ----------
    s: str
        variable to substitute
    local_vars: dict
        dict of variables
    """
    # Resolve variable substitution within the local_vars dict
    local_vars = _simple_subst_vars(local_vars, local_vars)

    def _subst (match):
        named = match.group("named")
        if named is not None:
            if named in local_vars:
                return str(local_vars[named])
            else:
                raise ValueError("Invalid variable '%s'" % named)
        if match.group("escaped") is not None:
            return _DELIM
        raise ValueError("This should not happen")

    def _do_subst(v):
        pattern_s = r"""
        %(delim)s(?:
            (?P<escaped>%(delim)s) |
            (?P<named>%(id)s)
        )""" % {"delim": r"\%s" % _DELIM, "id": _IDPATTERN}
        pattern = re.compile(pattern_s, re.VERBOSE)
        return pattern.sub(_subst, v)

    try:
        return _do_subst(s)
    except KeyError:
        raise ValueError("invalid variable '$%s'" % ex_stack()) class BuildYakuContext(BuildContext):
    def __init__(self, global_context, cmd_argv, options_context, pkg, run_node):
        from bento.commands.build_yaku import build_extension, build_compiled_library

        super(BuildYakuContext, self).__init__(global_context, cmd_argv, options_context, pkg, run_node)
        build_path = run_node._ctx.bldnode.path_from(run_node)
        source_path = run_node._ctx.srcnode.path_from(run_node)
        self.yaku_context = yaku.context.get_bld(src_path=source_path, build_path=build_path)

        o, a = options_context.parser.parse_args(cmd_argv)
        if o.jobs:
            jobs = int(o.jobs)
        else:
            jobs = 1
        self.verbose = o.verbose
        self.jobs = jobs

        def _builder_factory(category, builder):
            def _build(extension, include_dirs=None, **kw):
                env = kw.get("env", {})
                if include_dirs:
                    env["include_dirs"] = include_dirs

                outputs = builder(self.yaku_context, extension, env=env)
                nodes = [self.build_node.make_node(o) for o in outputs]

                p = self.build_node.find_node(nodes[0].parent.bldpath())
                relative_name = extension.name.rsplit(".")[-1]
                full_name = translate_name(relative_name, p, self.build_node)

                from_node = p
                pkg_dir = op.dirname(full_name.replace('.', op.sep))
                target_dir = op.join('$sitedir', pkg_dir)
                self.outputs_registry.register_outputs(category, full_name, nodes,
                                                       from_node, target_dir)
            return _build

        self.builder_registry.register_category("extensions",
            _builder_factory("extensions", build_extension))
        self.builder_registry.register_category("compiled_libraries",
            _builder_factory("compiled_libraries", build_compiled_library))

    def finish(self):
        super(BuildYakuContext, self).finish()
        self.yaku_context.store()

    def compile(self):
        super(BuildYakuContext, self).compile()

        import yaku.task_manager
        bld = self.yaku_context
        bld.env["VERBOSE"] = self.verbose

        reg = self.builder_registry

        for category in ["extensions", "compiled_libraries"]:
            for name, item in self._node_pkg.iter_category(category):
                builder = reg.builder(category, name)
                self.pre_recurse(item.ref_node)
                try:
                    item = item.extension_from(item.ref_node)
                    builder(item)
                finally:
                    self.post_recurse()

        task_manager = yaku.task_manager.TaskManager(bld.tasks)
        if self.jobs < 2:
            runner = yaku.scheduler.SerialRunner(bld, task_manager)
        else:
            runner = yaku.scheduler.ParallelRunner(bld, task_manager, self.jobs)
        runner.start()
        runner.run()

        # TODO: inplace support

    def pre_recurse(self, local_node):
        super(BuildYakuContext, self).pre_recurse(local_node)
        self._old_path = self.yaku_context.path
        # FIXME: we should not modify yaku context, but add current node +
        # recurse support to yaku instead
        # Gymnastic to make a *yaku* node from a *bento* node
        self.yaku_context.path = self.yaku_context.path.make_node(self.local_node.path_from(self.top_node))

    def post_recurse(self):
        self.yaku_context.path = self._old_path
        super(BuildYakuContext, self).post_recurse() class ConfigureYakuContext(ConfigureContext):
    def __init__(self, global_context, cmd_argv, options_context, pkg, run_node):
        super(ConfigureYakuContext, self).__init__(global_context, cmd_argv, options_context, pkg, run_node)
        build_path = run_node._ctx.bldnode.path_from(run_node)
        source_path = run_node._ctx.srcnode.path_from(run_node)
        self.yaku_context = yaku.context.get_cfg(src_path=source_path, build_path=build_path)

    def configure(self):
        extensions = get_extensions(self.pkg, self.run_node)
        libraries = get_compiled_libraries(self.pkg, self.run_node)

        yaku_ctx = self.yaku_context
        if extensions or libraries:
            for t in ["ctasks", "pyext"]:
                try:
                    yaku_ctx.use_tools([t])
                except yaku.errors.ConfigurationFailure:
                    e = extract_exception()
                    raise ConfigurationError(str(e))

    def finish(self):
        super(ConfigureYakuContext, self).finish()
        self.yaku_context.store()

    def pre_recurse(self, local_node):
        super(ConfigureYakuContext, self).pre_recurse(local_node)
        self._old_path = self.yaku_context.path
        self.yaku_context.path = self.yaku_context.path.make_node(self.local_node.path_from(self.top_node))

    def post_recurse(self):
        self.yaku_context.path = self._old_path
        super(ConfigureYakuContext, self).post_recurse() class YakuBackend(AbstractBackend):
    def register_command_contexts(self, context):
        context.register_command_context("configure", ConfigureYakuContext)
        context.register_command_context("build", BuildYakuContext) class PreHookWrapper(_HookWrapperBase):
    pass def run_command_in_context(context, cmd, pre_hooks=None, post_hooks=None):
    """Run the given command instance with the hooks within its context. """
    if pre_hooks is None:
        pre_hooks = []
    if post_hooks is None:
        post_hooks = []

    top_node = context.top_node
    cmd_funcs = [(cmd.run, top_node.abspath())]

    def _run_hooks(hooks):
        for hook in hooks:
            local_node = top_node.find_dir(relpath(hook.local_dir, top_node.abspath()))
            context.pre_recurse(local_node)
            try:
                hook(context)
            finally:
                context.post_recurse()

    context.init()
    try:
        cmd.init(context)

        _run_hooks(pre_hooks)

        context.configure()

        while cmd_funcs:
            cmd_func, local_dir = cmd_funcs.pop(0)
            local_node = top_node.find_dir(relpath(local_dir, top_node.abspath()))
            context.pre_recurse(local_node)
            try:
                cmd_func(context)
            finally:
                context.post_recurse()

        _run_hooks(post_hooks)

        cmd.finish(context)
    finally:
        context.finish()

    return cmd, context class OptionsContext(object):
    @classmethod
    def from_command(cls, cmd):
        usage = cmd.long_descr
        ret = cls(usage=usage)
        for o in cmd.common_options:
            ret.add_option(o)
        return ret

    def __init__(self, usage=None):
        kw = {"add_help_option": False, "formatter": IndentedHelpFormatter()}
        if usage is not None:
            kw["usage"] = usage
        self.parser = optparse.OptionParser(**kw)
        self._groups = {}
        self._is_setup = False

    def setup(self, package_options):
        self.add_group("build_customization", "Build customization")
        opt = optparse.Option("--use-distutils", help="Build extensions with distutils",
                              action="store_true")
        self.add_option(opt, "build_customization")

        self._is_setup = True

    def add_option(self, option, group=None):
        if group is None:
            self.parser.add_option(option)
        else:
            if group in self._groups:
                self._groups[group].add_option(option)
            else:
                raise ValueError("Unknown option group %r" % group)

    def has_group(self, group):
        return group in self._groups

    def add_group(self, name, title):
        grp = optparse.OptionGroup(self.parser, title)
        self._groups[name] = grp
        self.parser.add_option_group(grp) class BuildCommand(Command):
    long_descr = """\
Purpose: build the project
Usage:   bentomaker build [OPTIONS]."""
    short_descr = "build the project."
    common_options = Command.common_options \
                        + [Option("-i", "--inplace",
                                  help="Build extensions in place", action="store_true"),
                           Option("-j", "--jobs",
                                  help="Parallel builds (yaku build only - EXPERIMENTAL)",
                                  dest="jobs", action="callback", callback=jobs_callback),
                           Option("-v", "--verbose",
                                  help="Verbose output (yaku build only)",
                                  action="store_true")]

    def run(self, ctx):
        p = ctx.options_context.parser
        o, a = p.parse_args(ctx.command_argv)
        if o.help:
            p.print_help()
            return

        ctx.compile()
        ctx.post_compile()

    def finish(self, ctx):
        super(BuildCommand, self).finish(ctx)
        n = ctx.build_node.make_node(BUILD_MANIFEST_PATH)
        ctx.section_writer.store(n.abspath(), ctx.pkg) def create_fake_package_from_bento_infos(top_node, bento_infos, bscripts=None):
    if bscripts is None:
        bscripts = {}
    for loc, content in bento_infos.items():
        n = top_node.make_node(loc)
        n.parent.mkdir()
        n.write(content)
    for loc, content in bscripts.items():
        n = top_node.make_node(loc)
        n.parent.mkdir()
        n.write(content)

    d = raw_parse(bento_infos["bento.info"])
    _kw, files = raw_to_pkg_kw(d, {}, None)
    subpackages = _kw.get("subpackages", {})

    py_modules = _kw.get("py_modules", [])
    if "extensions" in _kw:
        extensions = list(_kw["extensions"].values())
    else:
        extensions = []
    if "compiled_libraries" in _kw:
        compiled_libraries = list(_kw["compiled_libraries"].values())
    else:
        compiled_libraries = []
    if "extra_source_files" in _kw:
        extra_source_files = list(_kw["extra_source_files"])
    else:
        extra_source_files = []

    packages = _kw.get("packages", [])
    for name, spkg in subpackages.items():
        n = top_node.search(name)
        n.write(bento_infos[name])
        d = n.parent
        for py_module in spkg.py_modules:
            m = d.make_node(py_module)
            py_modules.append(m.path_from(top_node))

        extensions.extend(flatten_extensions(top_node, spkg))
        compiled_libraries.extend(flatten_compiled_libraries(top_node, spkg))
        packages.extend(flatten_packages(top_node, spkg))

    return create_fake_package(top_node, packages, py_modules, extensions, compiled_libraries,
                               extra_source_files) def create_fake_package_from_bento_info(top_node, bento_info):
    d = raw_parse(bento_info)
    _kw, files = raw_to_pkg_kw(d, {}, None)
    kw = {}
    if "extensions" in _kw:
        kw["extensions"] = _kw["extensions"].values()
    if "py_modules" in _kw:
        kw["modules"] = _kw["py_modules"]
    if "packages" in _kw:
        kw["packages"] = _kw["packages"]
    if "compiled_libraries" in _kw:
        kw["compiled_libraries"] = _kw["compiled_libraries"].values()
    if "extra_source_files" in _kw:
        kw["extra_source_files"] = _kw["extra_source_files"]
    if "sub_directory" in _kw:
        kw["sub_directory"] = _kw["sub_directory"]
    if "data_files" in _kw:
        kw["data_files"] = _kw["data_files"]
    return create_fake_package(top_node, **kw) def require_c_compiler(builder="yaku"):
    if builder == "yaku":
        return _require_c_compiler_yaku()
    elif builder == "distutils":
        return _require_c_compiler_distutils()
    else:
        raise ValueError("Unrecognized builder: %r" % builder)
import os
import sys
import shutil
import tempfile
import os.path as op
        import bento.backends.waf_backend
from six.moves \
    import \
        cStringIO
from bento.compat.api.moves \
    import \
        unittest
from bento.core.options \
    import \
        PackageOptions
from bento.core.package \
    import \
        PackageDescription
from bento.core.testing\
    import \
        expected_failure, skip_if
from bento.core.node \
    import \
        create_base_nodes
from bento.utils.utils \
    import \
        subst_vars
from bento.backends.distutils_backend \
    import \
        DistutilsBuildContext, DistutilsConfigureContext
from bento.backends.yaku_backend \
    import \
        BuildYakuContext, ConfigureYakuContext, YakuBackend
from bento.commands.hooks \
    import \
        PreHookWrapper
from bento.commands.wrapper_utils \
    import \
        run_command_in_context
from bento.commands.options \
    import \
        OptionsContext
from bento.commands.build \
    import \
        BuildCommand
from bento.core.testing \
    import \
        create_fake_package_from_bento_infos, create_fake_package_from_bento_info, \
        require_c_compiler
from bento.commands.tests.utils \
    import \
        prepare_configure, prepare_build, EncodedStringIO, \
        prepare_command, create_global_context, prepare_package
from bento.errors \
    import \
        UsageException
        from bento.commands.build_distutils import DistutilsBuilder
        from bento.backends.waf_backend import make_stream_logger
        from bento.backends.waf_backend import WafBackend
            from bento.backends.waf_backend import ConfigureWafContext, BuildWafContext
        from bento.backends.waf_backend import make_stream_logger
        from bento.backends.waf_backend import WafBackend
        from bento.backends.distutils_backend import DistutilsBuildContext
"""
        n = self.top_node.make_node(op.join("foo", "__package_info.py.in"))
        n.parent.mkdir()
        n.write("""\
NAME = $NAME
""")
        self._execute_build(bento_info)
        package_info = self.build_node.find_node(op.join("foo", "__package_info.py"))

        r_package_info = """\
NAME = "foo"
"""
        self.assertEqual(package_info.read(), r_package_info)

class TestBuildDirectoryBase(unittest.TestCase):
    def setUp(self):
        self.d = tempfile.mkdtemp()

        try:
            self.top_node, self.build_node, self.run_node = create_base_nodes(self.d,
                    os.path.join(self.d, "yoyobuild"))

            self.old_dir = os.getcwd()
            os.chdir(self.d)
        except:
            shutil.rmtree(self.d)
            raise

    def tearDown(self):
        os.chdir(self.old_dir)
        shutil.rmtree(self.d)

class TestBuildDirectory(TestBuildDirectoryBase):
    def test_simple_yaku(self):
        top_node = self.top_node

        create_fake_package_from_bento_info(top_node, BENTO_INFO_WITH_EXT)
        conf, configure = prepare_configure(top_node, BENTO_INFO_WITH_EXT, ConfigureYakuContext)
        run_command_in_context(conf, configure)

        build = BuildCommand()
        opts = OptionsContext.from_command(build)

        bld = BuildYakuContext(None, [], opts, conf.pkg, top_node)
        run_command_in_context(bld, build)

    def test_simple_distutils(self):
        top_node = self.top_node

        create_fake_package_from_bento_info(top_node, BENTO_INFO_WITH_EXT)
        conf, configure = prepare_configure(top_node, BENTO_INFO_WITH_EXT, DistutilsConfigureContext)
        run_command_in_context(conf, configure)

        build = BuildCommand()
        opts = OptionsContext.from_command(build)

        bld = DistutilsBuildContext(None, [], opts, conf.pkg, top_node)
        run_command_in_context(bld, build)

class TestBuildDirectoryWaf(TestBuildDirectoryBase):
    def setUp(self):
        TestBuildDirectoryBase.setUp(self)

        self._stderr = sys.stderr
        self._stdout = sys.stdout

        sys.stdout = EncodedStringIO()
        sys.stderr = EncodedStringIO()

    def tearDown(self):
        sys.stderr = self._stderr
        sys.stdout = self._stdout

        TestBuildDirectoryBase.tearDown(self)

    @skip_no_waf
    def test_simple_waf(self):

        top_node = self.top_node

        package = PackageDescription.from_string(BENTO_INFO_WITH_EXT)
        package_options = PackageOptions.from_string(BENTO_INFO_WITH_EXT)

        create_fake_package_from_bento_info(top_node, BENTO_INFO_WITH_EXT)
        top_node.make_node("bento.info").safe_write(BENTO_INFO_WITH_EXT)

        global_context = create_global_context(package, package_options, WafBackend())
        conf, configure = prepare_command(global_context, "configure", [], package, top_node)
        run_command_in_context(conf, configure)

        bld, build = prepare_command(global_context, "build", [], package, top_node)
        bld.waf_context.logger = make_stream_logger("build", cStringIO())
        run_command_in_context(bld, build)

class _SandboxMixin(unittest.TestCase):
    def setUp(self):
        self.new_cwd = tempfile.mkdtemp()
        try:
            self.old_cwd = os.getcwd()
            os.chdir(self.new_cwd)
        except:
            shutil.rmtree(self.new_cwd)
            raise

    def tearDown(self):
        os.chdir(self.old_cwd)
        shutil.rmtree(self.new_cwd)

class TestOutputRegistration(_SandboxMixin):
    def setUp(self):
        super(TestOutputRegistration, self).setUp()
        self.top_node, self.build_node, self.run_node = create_base_nodes()

    def _run_build_with_pre_hook(self, hook_func):
        package = PackageDescription.from_string(BENTO_INFO)
        global_context = prepare_package(self.top_node, BENTO_INFO)

        conf, configure = prepare_command(global_context, "configure", [], package, self.top_node)
        run_command_in_context(conf, configure)


        pre_hook = PreHookWrapper(hook_func, self.build_node.path_from(self.top_node), self.top_node.abspath())