#!/usr/bin/env python # Copyright (C) 2013 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * 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. # * Neither the name of Google Inc. 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 # OWNER 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. import copy import os import sys if sys.version_info.major == 2: import cPickle as pickle else: import pickle from blinkbuild.name_style_converter import NameStyleConverter import make_runtime_features_utilities as util import json5_generator import template_expander class BaseRuntimeFeatureWriter(json5_generator.Writer): # |class_name| should be passed as a template input to generate the target # class. Set this variable if the template generates a class. class_name = None # |file_basename| must be set by subclasses since it is used to generate # the header guard. file_basename = None def __init__(self, json5_file_path, output_dir): super(BaseRuntimeFeatureWriter, self).__init__(json5_file_path, output_dir) # Subclasses should add generated output files and their contents to this dict. self._outputs = {} assert self.file_basename self._features = self.json5_file.name_dictionaries origin_trial_set = util.origin_trials(self._features) # Make sure the resulting dictionaries have all the keys we expect. for feature in self._features: feature['in_origin_trial'] = str( feature['name']) in origin_trial_set feature['data_member_name'] = self._data_member_name( feature['name']) # If 'status' is a dict, add the values for all the not-mentioned platforms too. if isinstance(feature['status'], dict): feature['status'] = self._status_with_all_platforms( feature['status']) # Specify the type of status feature['status_type'] = "dict" if isinstance( feature['status'], dict) else "str" self._origin_trial_features = [ feature for feature in self._features if feature['in_origin_trial'] ] self._header_guard = self.make_header_guard(self._relative_output_dir + self.file_basename + '.h') @staticmethod def _data_member_name(str_or_converter): converter = NameStyleConverter(str_or_converter) if type( str_or_converter) is str else str_or_converter return converter.to_class_data_member(prefix='is', suffix='enabled') def _feature_sets(self): # Another way to think of the status levels is as "sets of features" # which is how we're referring to them in this generator. return self.json5_file.parameters['status']['valid_values'] def _status_with_all_platforms(self, status): new_status = copy.deepcopy(status) default = new_status['default'] if 'default' in new_status else '' new_status['default'] = default for platform in self._platforms(): if platform not in new_status: new_status[platform] = default return new_status def _platforms(self): # Remove all occurrences of 'default' from 'valid_keys' platforms = self.json5_file.parameters['status']['valid_keys'] return [platform for platform in platforms if platform != 'default'] class RuntimeFeatureWriter(BaseRuntimeFeatureWriter): class_name = 'RuntimeEnabledFeatures' file_basename = 'runtime_enabled_features' def __init__(self, json5_file_path, output_dir): super(RuntimeFeatureWriter, self).__init__(json5_file_path, output_dir) self._outputs = { (self.file_basename + '.h'): self.generate_header, (self.file_basename + '.cc'): self.generate_implementation, } # Write features to file for bindings generation self._write_features_to_pickle_file(output_dir) def _write_features_to_pickle_file(self, platform_output_dir): # TODO(yashard): Get the file path from args instead of hardcoding it. file_name = os.path.join(platform_output_dir, '..', 'build', 'scripts', 'runtime_enabled_features.pickle') features_map = {} for feature in self._features: features_map[str(feature['name'])] = { 'in_origin_trial': feature['in_origin_trial'] } if os.path.isfile(file_name): with open(os.path.abspath(file_name)) as pickle_file: # pylint: disable=broad-except try: if pickle.load(pickle_file) == features_map: return except Exception: # If trouble unpickling, overwrite pass with open(os.path.abspath(file_name), 'wb') as pickle_file: pickle.dump(features_map, pickle_file) def _template_inputs(self): return { 'features': self._features, 'feature_sets': self._feature_sets(), 'platforms': self._platforms(), 'input_files': self._input_files, 'origin_trial_controlled_features': self._origin_trial_features, 'header_guard': self._header_guard, } @template_expander.use_jinja('templates/' + file_basename + '.h.tmpl') def generate_header(self): return self._template_inputs() @template_expander.use_jinja('templates/' + file_basename + '.cc.tmpl') def generate_implementation(self): return self._template_inputs() class RuntimeFeatureTestHelpersWriter(BaseRuntimeFeatureWriter): class_name = 'ScopedRuntimeEnabledFeatureForTest' file_basename = 'runtime_enabled_features_test_helpers' def __init__(self, json5_file_path, output_dir): super(RuntimeFeatureTestHelpersWriter, self).__init__( json5_file_path, output_dir) self._outputs = { ('testing/' + self.file_basename + '.h'): self.generate_header } def _template_inputs(self): return { 'features': self._features, 'input_files': self._input_files, 'header_guard': self._header_guard, } @template_expander.use_jinja('templates/' + file_basename + '.h.tmpl') def generate_header(self): return self._template_inputs() if __name__ == '__main__': json5_generator.Maker(RuntimeFeatureWriter).main() json5_generator.Maker(RuntimeFeatureTestHelpersWriter).main()