OLD | NEW |
(Empty) | |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import functools |
| 6 import collections |
| 7 import contextlib |
| 8 import json |
| 9 |
| 10 from recipe_engine import recipe_api |
| 11 from recipe_engine import util as recipe_util |
| 12 from recipe_engine import config_types |
| 13 |
| 14 |
| 15 class JsonOutputPlaceholder(recipe_util.Placeholder): |
| 16 """JsonOutputPlaceholder is meant to be a placeholder object which, when added |
| 17 to a step's cmd list, will be replaced by annotated_run with the path to a |
| 18 temporary file (e.g. /tmp/tmp4lp1qM) which will exist only for the duration of |
| 19 the step. If the script requires a flag (e.g. --output-json /path/to/file), |
| 20 you must supply that flag yourself in the cmd list. |
| 21 |
| 22 This placeholder can be optionally added when you use the Steps.step() |
| 23 method in this module. |
| 24 |
| 25 FIXME |
| 26 After the termination of the step, this file is expected to contain a valid |
| 27 JSON document, which will be set as the json.output for that step in the |
| 28 step_history OrderedDict passed to your recipe generator. |
| 29 """ |
| 30 def __init__(self, api, add_json_log): |
| 31 self.raw = api.m.raw_io.output('.json') |
| 32 self.add_json_log = add_json_log |
| 33 super(JsonOutputPlaceholder, self).__init__() |
| 34 |
| 35 @property |
| 36 def backing_file(self): |
| 37 return self.raw.backing_file |
| 38 |
| 39 def render(self, test): |
| 40 return self.raw.render(test) |
| 41 |
| 42 def result(self, presentation, test): |
| 43 raw_data = self.raw.result(presentation, test) |
| 44 |
| 45 valid = False |
| 46 ret = None |
| 47 try: |
| 48 ret = json.loads(raw_data, object_pairs_hook=collections.OrderedDict) |
| 49 valid = True |
| 50 # TypeError is raised when raw_data is None, which can happen if the json |
| 51 # file was not created. We then correctly handle this as invalid result. |
| 52 except (ValueError, TypeError): # pragma: no cover |
| 53 pass |
| 54 |
| 55 if self.add_json_log: |
| 56 key = self.name + ('' if valid else ' (invalid)') |
| 57 with contextlib.closing(recipe_util.StringListIO()) as listio: |
| 58 json.dump(ret, listio, indent=2, sort_keys=True) |
| 59 presentation.logs[key] = listio.lines |
| 60 |
| 61 return ret |
| 62 |
| 63 |
| 64 class JsonApi(recipe_api.RecipeApi): |
| 65 def __init__(self, **kwargs): |
| 66 super(JsonApi, self).__init__(**kwargs) |
| 67 self.loads = json.loads |
| 68 @functools.wraps(json.dumps) |
| 69 def dumps(*args, **kwargs): |
| 70 kwargs['sort_keys'] = True |
| 71 kwargs.setdefault('default', config_types.json_fixup) |
| 72 return json.dumps(*args, **kwargs) |
| 73 self.dumps = dumps |
| 74 |
| 75 def is_serializable(self, obj): |
| 76 """Returns True if the object is JSON-serializable.""" |
| 77 try: |
| 78 self.dumps(obj) |
| 79 return True |
| 80 except Exception: |
| 81 return False |
| 82 |
| 83 @recipe_util.returns_placeholder |
| 84 def input(self, data): |
| 85 """A placeholder which will expand to a file path containing <data>.""" |
| 86 return self.m.raw_io.input(self.dumps(data), '.json') |
| 87 |
| 88 @recipe_util.returns_placeholder |
| 89 def output(self, add_json_log=True): |
| 90 """A placeholder which will expand to '/tmp/file'.""" |
| 91 return JsonOutputPlaceholder(self, add_json_log) |
| 92 |
| 93 # TODO(you): This method should be in the `file` recipe_module |
| 94 def read(self, name, path, **kwargs): |
| 95 """Returns a step that reads a JSON file.""" |
| 96 return self.m.python.inline( |
| 97 name, |
| 98 """ |
| 99 import shutil |
| 100 import sys |
| 101 shutil.copy(sys.argv[1], sys.argv[2]) |
| 102 """, |
| 103 args=[path, self.output()], |
| 104 add_python_log=False, |
| 105 **kwargs |
| 106 ) |
| 107 |
| 108 def property_args(self): |
| 109 """Return --build-properties and --factory-properties arguments. LEGACY! |
| 110 |
| 111 Since properties is the merge of build_properties and factory_properties, |
| 112 pass the merged dict as both arguments. |
| 113 |
| 114 It's vastly preferable to have your recipe only pass the bare minimum |
| 115 of arguments to steps. Passing property objects obscures the data that |
| 116 the script actually consumes from the property object. |
| 117 """ |
| 118 prop_str = self.dumps(dict(self.m.properties.legacy())) |
| 119 return [ |
| 120 '--factory-properties', prop_str, |
| 121 '--build-properties', prop_str |
| 122 ] |
OLD | NEW |