| Index: scripts/slave/recipe_modules/json/api.py
|
| diff --git a/scripts/slave/recipe_modules/json/api.py b/scripts/slave/recipe_modules/json/api.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c41bd42e17fb9f05b8fcca8792acb2d9c9b73bd6
|
| --- /dev/null
|
| +++ b/scripts/slave/recipe_modules/json/api.py
|
| @@ -0,0 +1,122 @@
|
| +# Copyright 2013 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import functools
|
| +import collections
|
| +import contextlib
|
| +import json
|
| +
|
| +from recipe_engine import recipe_api
|
| +from recipe_engine import util as recipe_util
|
| +from recipe_engine import config_types
|
| +
|
| +
|
| +class JsonOutputPlaceholder(recipe_util.Placeholder):
|
| + """JsonOutputPlaceholder is meant to be a placeholder object which, when added
|
| + to a step's cmd list, will be replaced by annotated_run with the path to a
|
| + temporary file (e.g. /tmp/tmp4lp1qM) which will exist only for the duration of
|
| + the step. If the script requires a flag (e.g. --output-json /path/to/file),
|
| + you must supply that flag yourself in the cmd list.
|
| +
|
| + This placeholder can be optionally added when you use the Steps.step()
|
| + method in this module.
|
| +
|
| + FIXME
|
| + After the termination of the step, this file is expected to contain a valid
|
| + JSON document, which will be set as the json.output for that step in the
|
| + step_history OrderedDict passed to your recipe generator.
|
| + """
|
| + def __init__(self, api, add_json_log):
|
| + self.raw = api.m.raw_io.output('.json')
|
| + self.add_json_log = add_json_log
|
| + super(JsonOutputPlaceholder, self).__init__()
|
| +
|
| + @property
|
| + def backing_file(self):
|
| + return self.raw.backing_file
|
| +
|
| + def render(self, test):
|
| + return self.raw.render(test)
|
| +
|
| + def result(self, presentation, test):
|
| + raw_data = self.raw.result(presentation, test)
|
| +
|
| + valid = False
|
| + ret = None
|
| + try:
|
| + ret = json.loads(raw_data, object_pairs_hook=collections.OrderedDict)
|
| + valid = True
|
| + # TypeError is raised when raw_data is None, which can happen if the json
|
| + # file was not created. We then correctly handle this as invalid result.
|
| + except (ValueError, TypeError): # pragma: no cover
|
| + pass
|
| +
|
| + if self.add_json_log:
|
| + key = self.name + ('' if valid else ' (invalid)')
|
| + with contextlib.closing(recipe_util.StringListIO()) as listio:
|
| + json.dump(ret, listio, indent=2, sort_keys=True)
|
| + presentation.logs[key] = listio.lines
|
| +
|
| + return ret
|
| +
|
| +
|
| +class JsonApi(recipe_api.RecipeApi):
|
| + def __init__(self, **kwargs):
|
| + super(JsonApi, self).__init__(**kwargs)
|
| + self.loads = json.loads
|
| + @functools.wraps(json.dumps)
|
| + def dumps(*args, **kwargs):
|
| + kwargs['sort_keys'] = True
|
| + kwargs.setdefault('default', config_types.json_fixup)
|
| + return json.dumps(*args, **kwargs)
|
| + self.dumps = dumps
|
| +
|
| + def is_serializable(self, obj):
|
| + """Returns True if the object is JSON-serializable."""
|
| + try:
|
| + self.dumps(obj)
|
| + return True
|
| + except Exception:
|
| + return False
|
| +
|
| + @recipe_util.returns_placeholder
|
| + def input(self, data):
|
| + """A placeholder which will expand to a file path containing <data>."""
|
| + return self.m.raw_io.input(self.dumps(data), '.json')
|
| +
|
| + @recipe_util.returns_placeholder
|
| + def output(self, add_json_log=True):
|
| + """A placeholder which will expand to '/tmp/file'."""
|
| + return JsonOutputPlaceholder(self, add_json_log)
|
| +
|
| + # TODO(you): This method should be in the `file` recipe_module
|
| + def read(self, name, path, **kwargs):
|
| + """Returns a step that reads a JSON file."""
|
| + return self.m.python.inline(
|
| + name,
|
| + """
|
| + import shutil
|
| + import sys
|
| + shutil.copy(sys.argv[1], sys.argv[2])
|
| + """,
|
| + args=[path, self.output()],
|
| + add_python_log=False,
|
| + **kwargs
|
| + )
|
| +
|
| + def property_args(self):
|
| + """Return --build-properties and --factory-properties arguments. LEGACY!
|
| +
|
| + Since properties is the merge of build_properties and factory_properties,
|
| + pass the merged dict as both arguments.
|
| +
|
| + It's vastly preferable to have your recipe only pass the bare minimum
|
| + of arguments to steps. Passing property objects obscures the data that
|
| + the script actually consumes from the property object.
|
| + """
|
| + prop_str = self.dumps(dict(self.m.properties.legacy()))
|
| + return [
|
| + '--factory-properties', prop_str,
|
| + '--build-properties', prop_str
|
| + ]
|
|
|