File: llbuild.py

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (320 lines) | stat: -rw-r--r-- 10,444 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

"""
Bindings for the llbuild C API.

This is incomplete, and has several serious issues.
"""

import cffi
import os
import re

###
# TODO
#
# 1. Deal with leaks of Task and Rule objects, because of the handle cycle. We
#    need the C-API to allow us to do cleanup on the Task and Delegate user
#    contex pointers.

###

class AbstractError(Exception):
    pass

###

ffi = cffi.FFI()

# Load the defs by reading the llbuild header directly.
data = open(os.path.join(
    os.path.dirname(__file__),
    "../../products/libllbuild/include/llbuild/llbuild.h")).read()
# Strip out the directives.
data = re.sub("^#.*", "", data, 0, re.MULTILINE)
data = re.sub("LLBUILD_EXPORT", "", data, 0, re.MULTILINE)
ffi.cdef(data)

# Load the dylib.
#
# FIXME: Need a way to find this.
libllbuild = ffi.dlopen(os.path.join(
    os.path.dirname(__file__),
    "../../build/lib/libllbuild.dylib"))

@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_start(context, engine_context, _task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.start(engine)

@ffi.callback(
    "void(void*, void*, llb_task_t*, uintptr_t, llb_data_t*)")
def _task_provide_value(context, engine_context, task, input_id, value):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    task.provide_value(engine, input_id,
                       str(ffi.buffer(value.data, value.length)))
    
@ffi.callback("void(void*, void*, llb_task_t*)")
def _task_inputs_available(context, engine_context, llb_task):
    task = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Notify the task.
    task.inputs_available(engine)

@ffi.callback("llb_task_t*(void*, void*)")
def _rule_create_task(context, engine_context):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    # Create the task.
    task = rule.create_task()
    assert isinstance(task, Task)

    # FIXME: Should we pass the context pointer separately from the delegate
    # structure, so it can be reused?
    delegate = ffi.new("llb_task_delegate_t*")
    delegate.context = task._handle
    delegate.start = _task_start
    delegate.provide_value = _task_provide_value
    delegate.inputs_available = _task_inputs_available

    task._task = libllbuild.llb_task_create(delegate[0])
    return task._task

@ffi.callback("bool(void*, void*, llb_rule_t*, llb_data_t*)")
def _rule_is_result_valid(context, engine_context, rule, value):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    return rule.is_result_valid(
        engine, str(ffi.buffer(value.data, value.length)))

@ffi.callback("void(void*, void*, llb_rule_status_kind_t)")
def _rule_update_status(context, engine_context, kind):
    rule = ffi.from_handle(context)
    engine = ffi.from_handle(engine_context)

    rule.update_status(engine, kind)

@ffi.callback("void(void*, llb_data_t*, llb_rule_t*)")
def _buildengine_lookup_rule(context, key, rule_out):
    engine = ffi.from_handle(context)
    rule = engine.delegate.lookup_rule(str(ffi.buffer(key.data, key.length)))

    # Initialize the rule result from the given object.
    assert isinstance(rule, Rule)

    # FIXME: What is to prevent the rule from being deallocated after this
    # point? We only are passing back a handle.
    rule_out.context = rule._handle
    rule_out.create_task = _rule_create_task
    rule_out.is_result_valid = _rule_is_result_valid
    rule_out.update_status = _rule_update_status
    
class _Data(object):
    """Wrapper for a key and its data."""
    def __init__(self, name):
        name = str(name)
        self.key = ffi.new("llb_data_t*")
        self.key.length = length = len(name)
        # Store in a local to keep alive.
        self._datap = ffi.new("char[]", length)
        self.key.data = self._datap
        # Copy in the data.
        ffi.buffer(self.key.data, length)[:] = name

class _HandledObject(object):
    _all_handles = []
    _handle_cache = None
    @property
    def _handle(self):
        # FIXME: This creates a cycle.
        if self._handle_cache is None:
            self._handle_cache = handle = ffi.new_handle(self)
            # FIXME: This leaks everything, but we are currently dropping a
            # handle somewhere.
            self._all_handles.append(handle)
        return self._handle_cache
            
###

def get_full_version():
    return ffi.string(libllbuild.llb_get_full_version_string())
        
class Task(_HandledObject):
    _task = None

    def start(self, engine):
        pass

    def provide_value(self, engine, input_id, value):
        # We consider it a runtime error for a task to receive a value if it
        # didn't override this callback.
        raise RuntimeError()

    def inputs_available(self, engine):
        raise AbstractError()

class Rule(_HandledObject):
    def create_task(self):
        raise AbstractError()

    def is_result_valid(self, engine, result):
        return True

    def update_status(self, engine, kind):
        pass

class BuildEngineDelegate(object):
    pass

class BuildEngine(_HandledObject):
    def __init__(self, delegate):
        # Store our delegate.
        self.delegate = delegate

        # Create the engine delegate.
        self._llb_delegate = ffi.new("llb_buildengine_delegate_t*")
        self._llb_delegate.context = self._handle
        self._llb_delegate.lookup_rule = _buildengine_lookup_rule

        # Create the underlying engine.
        self._engine = libllbuild.llb_buildengine_create(self._llb_delegate[0])

    ###
    # Client API

    def build(self, key):
        """
        build(key) -> value

        Compute the result for the given key.
        """

        # Create a data version of the key.
        key_data = _Data(key)

        # Request the build.
        result = ffi.new("llb_data_t*")
        libllbuild.llb_buildengine_build(self._engine, key_data.key, result)

        # Copy out the result.
        return str(ffi.buffer(result.data, result.length))

    def attach_db(self, path, schema_version=1):
        """
        attach_db(path, schema_version=1) -> None

        Attach a database to store the build results.
        """

        error = ffi.new("char *")
        path = _Data(path)
        if not libllbuild.llb_buildengine_attach_db(
                self._engine, path.key, schema_version, error):
            raise IOError("unable to attach database; %r" % (
                ffi.string(error),))
        
    def close(self):
        """
        close() -- Close the engine connection.
        """
        self._handle_cache = None
        libllbuild.llb_buildengine_destroy(self._engine)
        self._engine = None

    ###
    # Task API

    def task_needs_input(self, task, key, input_id=0):
        """\
task_needs_input(task, key, input_id)

Specify the given \arg Task depends upon the result of computing \arg key.

The result, when available, will be provided to the task via \see
provide_value(), supplying the provided \arg input_id to allow the
task to identify the particular input.

NOTE: It is an unchecked error for a task to request the same input value
multiple times.

\param input_id An arbitrary value that may be provided by the client to
use in efficiently associating this input. The range of this parameter is
intentionally chosen to allow a pointer to be provided."""
        key = _Data(key)
        libllbuild.llb_buildengine_task_needs_input(
            self._engine, task._task, key.key, input_id)

    def task_must_follow(self, task, key):
        """\
task_must_follow(task, key)

Specify that the given \arg task must be built subsequent to the
computation of \arg key.

The value of the computation of \arg key is not available to the task, and the
only guarantee the engine provides is that if \arg key is computed during a
build, then \arg Task will not be computed until after it."""
        key = _Data(key)
        libllbuild.llb_buildengine_task_must_follow(
            self._engine, task._task, key.key)

    def task_discovered_dependency(self, task, key):
        """\
task_discovered_dependency(task, key)

Inform the engine of an input dependency that was discovered by the task
during its execution, a la compiler generated dependency files.

This call may only be made after a task has received all of its inputs;
inputs discovered prior to that point should simply be requested as normal
input dependencies.

Such a dependency is not used to provide additional input to the task,
rather it is a way for the task to report an additional input which should
be considered the next time the rule is evaluated. The expected use case
for a discovered dependency is is when a processing task cannot predict
all of its inputs prior to being run, but can presume that any unknown
inputs already exist. In such cases, the task can go ahead and run and can
report the all of the discovered inputs as it executes. Once the task is
complete, these inputs will be recorded as being dependencies of the task
so that it will be recomputed when any of the inputs change.

It is legal to call this method from any thread, but the caller is
responsible for ensuring that it is never called concurrently for the same
task."""
        key = _Data(key)
        libllbuild.llb_buildengine_task_must_follow(
            self._engine, task._task, key.key)

    def task_is_complete(self, task, value, force_change=False):
        """\
task_is_complete(task, value, force_change=False)

Called by a task to indicate it has completed and to provide its value.

It is legal to call this method from any thread.

\param value The new value for the task's rule.

\param force_change If true, treat the value as changed and trigger
dependents to rebuild, even if the value itself is not different from the
prior result."""
        # Complete the result.
        value = _Data(value)
        libllbuild.llb_buildengine_task_is_complete(
            self._engine, task._task, value.key, force_change)

__all__ = ['get_full_version', 'BuildEngine', 'Rule', 'Task']