# -*- mode: python -*-
# Copyright (C) 2016 Niklas Rosenstein
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
'''
.. autofunction:: alias
.. autofunction:: run
.. autofunction:: render_template
'''
__all__ = ['alias', 'run', 'render_template']
from os import environ
from craftr import *
from craftr.ext import platform
from craftr.ext.compiler import gen_output
import abc
import craftr
import sys
[docs]def alias(*targets, target_name=None):
"""
Create an alias target that causes all specified "targets"
to be built.
:param targets: The targets to create an alias for. You
may pass None for an element, in which case it is ignored.
:param target_name: Alternative target name.
"""
# TODO: This is a placeholder for a better alias implementation.
inputs = []
for target in targets:
if target:
inputs.extend(target.inputs)
builder = TargetBuilder(inputs, [], {}, name=target_name)
return builder.create_target('echo',
description='Collective alias target: {0!r}'.format(builder.name))
[docs]def run(commands, args=(), inputs=(), outputs=None, cwd=None,
pool=None, description=None, target_name=None, multiple=False):
"""
This function creates a :class:`Target` that runs a custom command.
The function is three different modes based on the first parameter.
1. If *commands* is a :class:`Target`, that target must list exactly
one file in its outputs and that file is assumed to be a binary and
will be executed by the target created by this function. The *args*
parameter may be a list of additional arguments for the program.
2. If *commands* is a list, it is handled as a list of commands,
never as a single command. Thus a string in the list represents a
complete command, as does a list of strings (representing the command
as its individual arguments).
3. If *commands* is a string, it will be treated as a single command.
If multiple commands need to be invoked,
:class:`TargetBuilder.write_multicommand_file` is used to create a
script to invoke multiple commands.
__Examples__
.. code-block:: python
main = ld.link(
output = 'main',
inputs = objects,
)
run = rules.run(main, args = [path.local('testfile.dat')])
.. code-block:: python
run = rules.run([
'command1 args11 args12 args13',
['command2', 'args21', 'args22', 'args23'],
], cwd = path.local('test'), multiple=True)
:param commands: A :class:`Target`, string or list of strings/command lists.
:param args: Additional program arguments when a :class:`Target` is
specified for *commands*.
:param inputs: A list of input files for the command. These can be
referenced using the Ninja variable ``%in`` in the command(s).
:param outputs: A list of outputs generated by the command. These
can be referenced using the Ninja variable ``%out`` in the command(s).
:param cwd: An optional working directory to switch to when executing
the command(s). If None is passed, the build directory is used.
:param pool: Override the default pool that the command is executed in.
If a :class:`Target` is passed for *commands*, this will default to
``console``.
:param description: Optional target description displayed when building
with Ninja.
:param multiple: True if *commands* is a list of commands. This will
cause a shell/batch script to be created and invoked by Ninja.
:param target_name: An optional override for the return target's name.
:return: A :class:`Target`.
"""
builder = TargetBuilder(inputs, [], {}, name = target_name)
program = None
if isinstance(commands, Target):
assert len(commands.outputs) == 1, "Target for rules.run() must specify exactly one output file"
program = path.abspath(commands.outputs[0])
commands = [program] + list(args)
pool = pool or 'console'
builder.target_attrs['explicit'] = True
multiple = False
elif isinstance(commands, str):
commands = [commands]
multiple = False
if not multiple:
# We don't need a multi command file for a single command.
if isinstance(commands, str):
command = commands
else:
command = shell.join(commands)
if cwd:
if platform.name == platform.WIN32:
command = ['cmd', '/c', 'cd', cwd, shell.safe('&&')] + command
else:
command = [shell.safe('('), 'cd', cwd, shell.safe('&&')] + command + [shell.safe(')')]
else:
command, program = builder.write_multicommand_file(commands, cwd=cwd)
inputs = builder.inputs or program
return builder.create_target(command, inputs, outputs, pool=pool,
description=description, explicit=True)
[docs]def render_template(template, output, context, env = None, target_name = None):
''' Creates a :func:`task` that renders the file *template*
using Jinja2 with the specified *context* to the *output* file.
.. code-block:: python
# craftr_module(my_project)
import jinja2
from craftr import path
from craftr.ext import rules
# We can use the render_template() task factory to render
# a Jinja2 template that outputs a linker script.
ld_script = rules.render_template(
template = path.local('my_project.ld.jinja2'),
output = 'test.html',
env = jinja2.Environment(
variable_start_string = '{$',
variable_end_string = '$}',
),
context = dict(
# Context variables here
)
)
:param template: Filename of a Jinja template.
:param output: Output filename.
:param context: Context dictionary.
:param env: A :class:`jinja2.Environment` object.
:param target_name: Optional target name. Automatically deduced
from the assigned variable if omitted.
'''
import jinja2
builder = TargetBuilder([template], [], {}, name = target_name)
if not env:
env = jinja2.Environment()
def _render(_inputs, _outputs):
with open(template) as fp:
tobj = env.from_string(fp.read())
with open(output, 'w') as fp:
fp.write(tobj.render(context))
return builder.create_target(_render, inputs = [template], outputs = [output])