algorithm.py 20.9 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

Samuel GAIST's avatar
Samuel GAIST committed
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
###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################

André Anjos's avatar
André Anjos committed
36

37
38
39
40
41
42
"""
=========
algorithm
=========

Validation for algorithms
43
44
45
46

Forward importing from :py:mod:`beat.backend.python.algorithm`
:py:class:`beat.backend.python.algorithm.Storage`
:py:class:`beat.backend.python.algorithm.Runner`
47
"""
André Anjos's avatar
André Anjos committed
48
49


50
import os
André Anjos's avatar
André Anjos committed
51
import six
52
import sys
53
import json
André Anjos's avatar
André Anjos committed
54
import numpy
55
import pkg_resources
André Anjos's avatar
André Anjos committed
56
57
58
59
60
61

from . import dataformat
from . import library
from . import schema
from . import prototypes

62
from beat.backend.python.algorithm import Storage
63
from beat.backend.python.algorithm import Runner  # noqa
64
from beat.backend.python.algorithm import Algorithm as BackendAlgorithm
André Anjos's avatar
André Anjos committed
65
66


67
def load_algorithm_prototype(prefix):
68
69
70
71
72
73

    prototype_data = pkg_resources.resource_string(__name__, "prototypes/algorithm.json")
    if sys.version_info < (3, 6):
        prototype_data = prototype_data.decode("utf-8")

    algorithm_data = json.loads(prototype_data)
74
    ref_dataformats = ["integer", "integers"]
75
76
    dataformat = None

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    for ref_dataformat in ref_dataformats:
        for root, dirs, _ in os.walk(os.path.join(prefix, "dataformats")):
            if ref_dataformat in dirs:
                dataformat_versions = sorted(
                    os.listdir(os.path.join(root, ref_dataformat))
                )
                version = dataformat_versions[-1].split(".")[0]
                dataformat = "{}/{}/{}".format(
                    os.path.basename(root), ref_dataformat, version
                )
                break

    if dataformat is None:
        raise RuntimeError(
            "Reference data formats [{}] not found".format(",".join(ref_dataformats))
        )
93
94
95
96
97
    algorithm_data["groups"][0]["inputs"]["in_data"]["type"] = dataformat
    algorithm_data["groups"][0]["outputs"]["out_data"]["type"] = dataformat
    return algorithm_data


98
class Algorithm(BackendAlgorithm):
Philip ABBET's avatar
Philip ABBET committed
99
    """Algorithms represent runnable components within the platform.
André Anjos's avatar
André Anjos committed
100

Philip ABBET's avatar
Philip ABBET committed
101
102
    This class can only parse the meta-parameters of the algorithm (i.e., input
    and output declaration, grouping, synchronization details, parameters and
103
104
    splittability). The actual algorithm is not directly treated by this class.
    It can, however, provide you with a loader for actually running the
André Anjos's avatar
André Anjos committed
105
    algorithmic code (see :py:meth:`.runner`).
André Anjos's avatar
André Anjos committed
106
107


Philip ABBET's avatar
Philip ABBET committed
108
    Parameters:
André Anjos's avatar
André Anjos committed
109

Philip ABBET's avatar
Philip ABBET committed
110
      prefix (str): Establishes the prefix of your installation.
André Anjos's avatar
André Anjos committed
111

André Anjos's avatar
André Anjos committed
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
      data (:py:class:`object`, Optional): The piece of data representing the
        algorithm. It must validate against the schema defined for algorithms.
        If a string is passed, it is supposed to be a valid path to an
        algorithm in the designated prefix area. If a tuple is passed (or a
        list), then we consider that the first element represents the algorithm
        declaration, while the second, the code for the algorithm (either in
        its source format or as a binary blob). If ``None`` is passed, loads
        our default prototype for algorithms (source code will be in Python).

      dataformat_cache (:py:class:`dict`, Optional): A dictionary mapping
        dataformat names to loaded dataformats. This parameter is optional and,
        if passed, may greatly speed-up algorithm loading times as dataformats
        that are already loaded may be re-used.

      library_cache (:py:class:`dict`, Optional): A dictionary mapping library
        names to loaded libraries. This parameter is optional and, if passed,
        may greatly speed-up library loading times as libraries that are
        already loaded may be re-used.
André Anjos's avatar
André Anjos committed
130
131


Philip ABBET's avatar
Philip ABBET committed
132
    Attributes:
André Anjos's avatar
André Anjos committed
133

Philip ABBET's avatar
Philip ABBET committed
134
      name (str): The algorithm name
André Anjos's avatar
André Anjos committed
135

Philip ABBET's avatar
Philip ABBET committed
136
137
      description (str): The short description string, loaded from the JSON
        file if one was set.
André Anjos's avatar
André Anjos committed
138

Philip ABBET's avatar
Philip ABBET committed
139
      documentation (str): The full-length docstring for this object.
André Anjos's avatar
André Anjos committed
140

Philip ABBET's avatar
Philip ABBET committed
141
142
      storage (object): A simple object that provides information about file
        paths for this algorithm
André Anjos's avatar
André Anjos committed
143

144
145
      dataformats (dict): A dictionary containing all pre-loaded dataformats
        used by this algorithm. Data format objects will be of type
Philip ABBET's avatar
Philip ABBET committed
146
        :py:class:`beat.core.dataformat.DataFormat`.
André Anjos's avatar
André Anjos committed
147

148
149
      libraries (dict): A mapping object defining other libraries this
        algorithm needs to load so it can work properly.
André Anjos's avatar
André Anjos committed
150

Philip ABBET's avatar
Philip ABBET committed
151
152
      uses (dict): A mapping object defining the required library import name
        (keys) and the full-names (values).
André Anjos's avatar
André Anjos committed
153

154
155
      parameters (dict): A dictionary containing all pre-defined parameters
        that this algorithm accepts.
André Anjos's avatar
André Anjos committed
156

Philip ABBET's avatar
Philip ABBET committed
157
158
      splittable (bool): A boolean value that indicates if this algorithm is
        automatically parallelizeable by our backend.
André Anjos's avatar
André Anjos committed
159

Philip ABBET's avatar
Philip ABBET committed
160
      input_map (dict): A dictionary where the key is the input name and the
161
162
        value, its type. All input names (potentially from different groups)
        are comprised in this dictionary.
André Anjos's avatar
André Anjos committed
163

Philip ABBET's avatar
Philip ABBET committed
164
      output_map (dict): A dictionary where the key is the output name and the
165
166
        value, its type. All output names (potentially from different groups)
        are comprised in this dictionary.
André Anjos's avatar
André Anjos committed
167

168
169
170
171
      results (dict): If this algorithm is actually an analyzer (i.e., there
        are no formal outputs, but results that must be saved by the platform),
        then this dictionary contains the names and data types of those
        elements.
André Anjos's avatar
André Anjos committed
172

Philip ABBET's avatar
Philip ABBET committed
173
174
      groups (dict): A list containing dictionaries with inputs and outputs
        belonging to the same synchronization group.
André Anjos's avatar
André Anjos committed
175

Philip ABBET's avatar
Philip ABBET committed
176
177
      errors (list): A list containing errors found while loading this
        algorithm.
André Anjos's avatar
André Anjos committed
178

Philip ABBET's avatar
Philip ABBET committed
179
180
      data (dict): The original data for this algorithm, as loaded by our JSON
        decoder.
André Anjos's avatar
André Anjos committed
181

Philip ABBET's avatar
Philip ABBET committed
182
183
      code (str): The code that is associated with this algorithm, loaded as a
        text (or binary) file.
André Anjos's avatar
André Anjos committed
184

Philip ABBET's avatar
Philip ABBET committed
185
    """
André Anjos's avatar
André Anjos committed
186

Philip ABBET's avatar
Philip ABBET committed
187
188
    def __init__(self, prefix, data, dataformat_cache=None, library_cache=None):
        super(Algorithm, self).__init__(prefix, data, dataformat_cache, library_cache)
André Anjos's avatar
André Anjos committed
189

Philip ABBET's avatar
Philip ABBET committed
190
191
    def _load(self, data, dataformat_cache, library_cache):
        """Loads the algorithm"""
André Anjos's avatar
André Anjos committed
192

Philip ABBET's avatar
Philip ABBET committed
193
194
195
        self.errors = []
        self.data = None
        self.code = None
André Anjos's avatar
André Anjos committed
196

Philip ABBET's avatar
Philip ABBET committed
197
198
        self._name = None
        self.storage = None
199
200
        self.dataformats = {}  # preloaded dataformats
        self.libraries = {}  # preloaded libraries
Philip ABBET's avatar
Philip ABBET committed
201
        code = None
André Anjos's avatar
André Anjos committed
202

203
        if data is None:  # loads prototype and validates it
André Anjos's avatar
André Anjos committed
204

Philip ABBET's avatar
Philip ABBET committed
205
206
            data = None
            code = None
André Anjos's avatar
André Anjos committed
207

208
        elif isinstance(data, (tuple, list)):  # user has passed individual info
André Anjos's avatar
André Anjos committed
209

210
            data, code = data  # break down into two components
André Anjos's avatar
André Anjos committed
211

212
        if isinstance(data, six.string_types):  # user has passed a file pointer
André Anjos's avatar
André Anjos committed
213

Philip ABBET's avatar
Philip ABBET committed
214
215
216
            self._name = data
            self.storage = Storage(self.prefix, self._name)
            if not self.storage.json.exists():
217
                self.errors.append("Algorithm declaration file not found: %s" % data)
Philip ABBET's avatar
Philip ABBET committed
218
                return
André Anjos's avatar
André Anjos committed
219

220
            data = self.storage.json.path  # loads data from JSON declaration
André Anjos's avatar
André Anjos committed
221

Philip ABBET's avatar
Philip ABBET committed
222
        # At this point, `data' can be a dictionary or ``None``
223
        if data is None:  # loads the default declaration for an algorithm
224
225
226
            algorithm_data = load_algorithm_prototype(self.prefix)
            self.data, self.errors = schema.validate("algorithm", algorithm_data)
            assert not self.errors, "\n  * %s" % "\n  *".join(self.errors)  # nosec
227
        else:  # just assign it
Philip ABBET's avatar
Philip ABBET committed
228
            # this runs basic validation, including JSON loading if required
229
            self.data, self.errors = schema.validate("algorithm", data)
André Anjos's avatar
André Anjos committed
230

231
232
        if self.errors:
            return  # don't proceed with the rest of validation
André Anjos's avatar
André Anjos committed
233

234
        if self.storage is not None:  # loading from the disk, check code
Philip ABBET's avatar
Philip ABBET committed
235
            if not self.storage.code.exists():
236
237
238
239
                if self.data["language"] != "cxx":
                    self.errors.append(
                        "Algorithm code not found: %s" % self.storage.code.path
                    )
Philip ABBET's avatar
Philip ABBET committed
240
241
242
                    return
            else:
                code = self.storage.code.load()
André Anjos's avatar
André Anjos committed
243

Philip ABBET's avatar
Philip ABBET committed
244
        # At this point, `code' can be a string (or a binary blob) or ``None``
245
        if code is None:  # loads the default code for an algorithm
246
247
            self.code = prototypes.binary_load("algorithm.py")
            self.data["language"] = "python"
André Anjos's avatar
André Anjos committed
248

249
        else:  # just assign it - notice that in this case, no language is set
Philip ABBET's avatar
Philip ABBET committed
250
            self.code = code
André Anjos's avatar
André Anjos committed
251

252
253
        if self.errors:
            return  # don't proceed with the rest of validation
André Anjos's avatar
André Anjos committed
254

Philip ABBET's avatar
Philip ABBET committed
255
        # if no errors so far, make sense out of the declaration data
256
        self.groups = self.data["groups"]
André Anjos's avatar
André Anjos committed
257

Philip ABBET's avatar
Philip ABBET committed
258
259
        # now we check for consistence
        self._check_endpoint_uniqueness()
André Anjos's avatar
André Anjos committed
260

Philip ABBET's avatar
Philip ABBET committed
261
        # create maps for easy access to data
262
263
264
265
266
267
268
269
270
271
272
273
274
        self.input_map = dict(
            [(k, v["type"]) for g in self.groups for k, v in g["inputs"].items()]
        )
        self.output_map = dict(
            [
                (k, v["type"])
                for g in self.groups
                for k, v in g.get("outputs", {}).items()
            ]
        )
        self.loop_map = dict(
            [(k, v["type"]) for g in self.groups for k, v in g.get("loop", {}).items()]
        )
André Anjos's avatar
André Anjos committed
275

Philip ABBET's avatar
Philip ABBET committed
276
277
        self._validate_required_dataformats(dataformat_cache)
        self._convert_parameter_types()
André Anjos's avatar
André Anjos committed
278

Philip ABBET's avatar
Philip ABBET committed
279
280
281
        # finally, the libraries
        self._validate_required_libraries(library_cache)
        self._check_language_consistence()
André Anjos's avatar
André Anjos committed
282

Philip ABBET's avatar
Philip ABBET committed
283
284
285
    def _check_endpoint_uniqueness(self):
        """Checks for name clashes accross input/output groups
        """
André Anjos's avatar
André Anjos committed
286

Philip ABBET's avatar
Philip ABBET committed
287
        all_input_names = []
288
289
        for group in self.groups:
            all_input_names.extend(group["inputs"].keys())
Philip ABBET's avatar
Philip ABBET committed
290
        if len(set(all_input_names)) != len(all_input_names):
291
292
293
294
            self.errors.append(
                "repeated input name in algorithm `%s' "
                "declaration: %s" % (self.name, ", ".join(all_input_names))
            )
André Anjos's avatar
André Anjos committed
295

Philip ABBET's avatar
Philip ABBET committed
296
297
298
        # all outputs must have unique names
        all_output_names = []
        for group in self.groups:
299
300
301
            if "outputs" not in group:
                continue
            all_output_names.extend(group["outputs"].keys())
Philip ABBET's avatar
Philip ABBET committed
302
        if len(set(all_output_names)) != len(all_output_names):
303
304
305
306
            self.errors.append(
                "repeated output name in algorithm `%s' "
                "declaration: %s" % (self.name, ", ".join(all_output_names))
            )
André Anjos's avatar
André Anjos committed
307

Philip ABBET's avatar
Philip ABBET committed
308
309
310
    def _validate_required_dataformats(self, dataformat_cache):
        """Makes sure we can load all requested formats
        """
André Anjos's avatar
André Anjos committed
311

Philip ABBET's avatar
Philip ABBET committed
312
        for group in self.groups:
André Anjos's avatar
André Anjos committed
313

314
315
316
            for name, input in group["inputs"].items():
                if input["type"] in self.dataformats:
                    continue
André Anjos's avatar
André Anjos committed
317

318
319
                if dataformat_cache and input["type"] in dataformat_cache:  # reuse
                    thisformat = dataformat_cache[input["type"]]
320
                else:  # load it
321
                    thisformat = dataformat.DataFormat(self.prefix, input["type"])
322
                    if dataformat_cache is not None:  # update it
323
                        dataformat_cache[input["type"]] = thisformat
André Anjos's avatar
André Anjos committed
324

325
                self.dataformats[input["type"]] = thisformat
André Anjos's avatar
André Anjos committed
326

Philip ABBET's avatar
Philip ABBET committed
327
                if thisformat.errors:
328
329
330
331
332
                    self.errors.append(
                        "found error validating data format `%s' "
                        "for input `%s' on algorithm `%s': %s"
                        % (input["type"], name, self.name, "\n".join(thisformat.errors))
                    )
André Anjos's avatar
André Anjos committed
333

334
335
            if "outputs" not in group:
                continue
André Anjos's avatar
André Anjos committed
336

337
338
339
            for name, output in group["outputs"].items():
                if output["type"] in self.dataformats:
                    continue
André Anjos's avatar
André Anjos committed
340

341
342
                if dataformat_cache and output["type"] in dataformat_cache:  # reuse
                    thisformat = dataformat_cache[output["type"]]
343
                else:  # load it
344
                    thisformat = dataformat.DataFormat(self.prefix, output["type"])
345
                    if dataformat_cache is not None:  # update it
346
                        dataformat_cache[output["type"]] = thisformat
André Anjos's avatar
André Anjos committed
347

348
                self.dataformats[output["type"]] = thisformat
André Anjos's avatar
André Anjos committed
349

Philip ABBET's avatar
Philip ABBET committed
350
                if thisformat.errors:
351
352
353
354
355
356
357
358
359
360
                    self.errors.append(
                        "found error validating data format `%s' "
                        "for output `%s' on algorithm `%s': %s"
                        % (
                            output["type"],
                            name,
                            self.name,
                            "\n".join(thisformat.errors),
                        )
                    )
André Anjos's avatar
André Anjos committed
361

Philip ABBET's avatar
Philip ABBET committed
362
        if self.results:
André Anjos's avatar
André Anjos committed
363

Philip ABBET's avatar
Philip ABBET committed
364
            for name, result in self.results.items():
André Anjos's avatar
André Anjos committed
365

366
                if result["type"].find("/") != -1:
André Anjos's avatar
André Anjos committed
367

368
369
                    if result["type"] in self.dataformats:
                        continue
André Anjos's avatar
André Anjos committed
370

371
372
                    if dataformat_cache and result["type"] in dataformat_cache:  # reuse
                        thisformat = dataformat_cache[result["type"]]
Philip ABBET's avatar
Philip ABBET committed
373
                    else:
374
                        thisformat = dataformat.DataFormat(self.prefix, result["type"])
375
                        if dataformat_cache is not None:  # update it
376
                            dataformat_cache[result["type"]] = thisformat
André Anjos's avatar
André Anjos committed
377

378
                    self.dataformats[result["type"]] = thisformat
André Anjos's avatar
André Anjos committed
379

Philip ABBET's avatar
Philip ABBET committed
380
                    if thisformat.errors:
381
382
383
384
385
386
387
388
389
390
                        self.errors.append(
                            "found error validating data format `%s' "
                            "for result `%s' on algorithm `%s': %s"
                            % (
                                result["type"],
                                name,
                                self.name,
                                "\n".join(thisformat.errors),
                            )
                        )
André Anjos's avatar
André Anjos committed
391

Philip ABBET's avatar
Philip ABBET committed
392
    def _convert_parameter_types(self):
393
394
        """Converts types to numpy equivalents, checks defaults, ranges and
        choices
Philip ABBET's avatar
Philip ABBET committed
395
        """
André Anjos's avatar
André Anjos committed
396

Philip ABBET's avatar
Philip ABBET committed
397
398
399
400
        def _try_convert(name, tp, value, desc):
            try:
                return tp.type(value)
            except Exception as e:
401
402
403
404
                self.errors.append(
                    "%s for parameter `%s' cannot be cast to type "
                    "`%s': %s" % (desc, name, tp.name, e)
                )
André Anjos's avatar
André Anjos committed
405

406
407
        if self.parameters is None:
            return
André Anjos's avatar
André Anjos committed
408

Philip ABBET's avatar
Philip ABBET committed
409
        for name, parameter in self.parameters.items():
410
411
            if parameter["type"] == "string":
                parameter["type"] = numpy.dtype("str")
Philip ABBET's avatar
Philip ABBET committed
412
            else:
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
                parameter["type"] = numpy.dtype(parameter["type"])

            if "range" in parameter:
                parameter["range"][0] = _try_convert(
                    name, parameter["type"], parameter["range"][0], "start of range"
                )
                parameter["range"][1] = _try_convert(
                    name, parameter["type"], parameter["range"][1], "end of range"
                )
                if parameter["range"][0] >= parameter["range"][1]:
                    self.errors.append(
                        "range for parameter `%s' has a start greater "
                        "then the end value (%r >= %r)"
                        % (name, parameter["range"][0], parameter["range"][1])
                    )

            if "choice" in parameter:
                for i, choice in enumerate(parameter["choice"]):
                    parameter["choice"][i] = _try_convert(
                        name,
                        parameter["type"],
                        parameter["choice"][i],
                        "choice[%d]" % i,
                    )

            if "default" in parameter:
                parameter["default"] = _try_convert(
                    name, parameter["type"], parameter["default"], "default"
                )

                if "range" in parameter:  # check range
                    if (
                        parameter["default"] < parameter["range"][0]
                        or parameter["default"] > parameter["range"][1]
                    ):
                        self.errors.append(
                            "default for parameter `%s' (%r) is not "
                            "within parameter range [%r, %r]"
                            % (
                                name,
                                parameter["default"],
                                parameter["range"][0],
                                parameter["range"][1],
                            )
                        )

                if "choice" in parameter:  # check choices
                    if parameter["default"] not in parameter["choice"]:
                        self.errors.append(
                            "default for parameter `%s' (%r) is not "
                            "a valid choice `[%s]'"
                            % (
                                name,
                                parameter["default"],
                                ", ".join(["%r" % k for k in parameter["choice"]]),
                            )
                        )
André Anjos's avatar
André Anjos committed
470

Philip ABBET's avatar
Philip ABBET committed
471
    def _validate_required_libraries(self, library_cache):
André Anjos's avatar
André Anjos committed
472

Philip ABBET's avatar
Philip ABBET committed
473
        # all used libraries must be loadable; cannot use self as a library
André Anjos's avatar
André Anjos committed
474

Philip ABBET's avatar
Philip ABBET committed
475
        if self.uses:
André Anjos's avatar
André Anjos committed
476

Philip ABBET's avatar
Philip ABBET committed
477
            for name, value in self.uses.items():
André Anjos's avatar
André Anjos committed
478

479
480
481
                self.libraries[value] = library_cache.setdefault(
                    value, library.Library(self.prefix, value, library_cache)
                )
André Anjos's avatar
André Anjos committed
482

Philip ABBET's avatar
Philip ABBET committed
483
                if not self.libraries[value].valid:
484
485
486
487
                    self.errors.append(
                        "referred library `%s' (%s) is not valid"
                        % (self.libraries[value].name, name)
                    )
André Anjos's avatar
André Anjos committed
488

Philip ABBET's avatar
Philip ABBET committed
489
    def _check_language_consistence(self):
André Anjos's avatar
André Anjos committed
490

Philip ABBET's avatar
Philip ABBET committed
491
        # all used libraries must be programmed with the same language
492
493
        if self.language == "unknown":
            return  # bail out on unknown language
André Anjos's avatar
André Anjos committed
494

Philip ABBET's avatar
Philip ABBET committed
495
        if self.uses:
André Anjos's avatar
André Anjos committed
496

497
            for name, library_name in self.uses.items():
André Anjos's avatar
André Anjos committed
498

499
500
                if library_name not in self.libraries:
                    continue  # invalid
André Anjos's avatar
André Anjos committed
501

502
503
504
505
506
507
                if self.libraries[library_name].data is None:
                    self.errors.append(
                        "language for used library `%s' cannot be "
                        "inferred as the library was not properly loaded"
                        % (library_name,)
                    )
Philip ABBET's avatar
Philip ABBET committed
508
                    continue
André Anjos's avatar
André Anjos committed
509

510
511
512
513
514
515
516
517
518
519
                if self.libraries[library_name].language != self.language:
                    self.errors.append(
                        "language for used library `%s' (`%s') "
                        "differs from current language for this algorithm (`%s')"
                        % (
                            library_name,
                            self.libraries[library_name].language,
                            self.language,
                        )
                    )