1#!/usr/bin/env python 2# Copyright 2015 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import json 7import os.path 8import sys 9import optparse 10 11_script_path = os.path.realpath(__file__) 12 13sys.path.insert(0, os.path.normpath(_script_path + "/../../json_comment_eater")) 14try: 15 import json_comment_eater 16finally: 17 sys.path.pop(0) 18 19sys.path.insert(0, os.path.normpath(_script_path + "/../../json_to_struct")) 20try: 21 import json_to_struct 22finally: 23 sys.path.pop(0) 24 25sys.path.insert( 26 0, 27 os.path.normpath(_script_path + "/../../../components/variations/service")) 28try: 29 import generate_ui_string_overrider 30finally: 31 sys.path.pop(0) 32 33_platforms = [ 34 'android', 35 'android_weblayer', 36 'android_webview', 37 'chromeos', 38 'freebsd', 39 'fuchsia', 40 'ios', 41 'linux', 42 'mac', 43 'windows', 44] 45 46_form_factors = [ 47 'desktop', 48 'phone', 49 'tablet', 50] 51 52# Convert a platform argument to the matching Platform enum value in 53# components/variations/proto/study.proto. 54def _PlatformEnumValue(platform): 55 assert platform in _platforms 56 return 'Study::PLATFORM_' + platform.upper() 57 58def _FormFactorEnumValue(form_factor): 59 assert form_factor in _form_factors 60 return 'Study::' + form_factor.upper() 61 62def _Load(filename): 63 """Loads a JSON file into a Python object and return this object.""" 64 with open(filename, 'r') as handle: 65 result = json.loads(json_comment_eater.Nom(handle.read())) 66 return result 67 68def _LoadFieldTrialConfig(filename, platforms): 69 """Loads a field trial config JSON and converts it into a format that can be 70 used by json_to_struct. 71 """ 72 return _FieldTrialConfigToDescription(_Load(filename), platforms) 73 74def _ConvertOverrideUIStrings(override_ui_strings): 75 """Converts override_ui_strings to formatted dicts.""" 76 overrides = [] 77 for ui_string, override in override_ui_strings.iteritems(): 78 overrides.append({ 79 'name_hash': generate_ui_string_overrider.HashName(ui_string), 80 'value': override 81 }) 82 return overrides 83 84def _CreateExperiment(experiment_data, 85 platforms, 86 form_factors, 87 is_low_end_device): 88 """Creates an experiment dictionary with all necessary information. 89 90 Args: 91 experiment_data: An experiment json config. 92 platforms: A list of platforms for this trial. This should be 93 a subset of |_platforms|. 94 form_factors: A list of form factors for this trial. This should be 95 a subset of |_form_factors|. 96 is_low_end_device: An optional parameter. This can either be True or 97 False. None if not specified. 98 99 Returns: 100 An experiment dict. 101 """ 102 experiment = { 103 'name': experiment_data['name'], 104 'platforms': [_PlatformEnumValue(p) for p in platforms], 105 'form_factors': [_FormFactorEnumValue(f) for f in form_factors], 106 } 107 if is_low_end_device is not None: 108 experiment['is_low_end_device'] = str(is_low_end_device).lower() 109 forcing_flags_data = experiment_data.get('forcing_flag') 110 if forcing_flags_data: 111 experiment['forcing_flag'] = forcing_flags_data 112 params_data = experiment_data.get('params') 113 if (params_data): 114 experiment['params'] = [{'key': param, 'value': params_data[param]} 115 for param in sorted(params_data.keys())]; 116 enable_features_data = experiment_data.get('enable_features') 117 if enable_features_data: 118 experiment['enable_features'] = enable_features_data 119 disable_features_data = experiment_data.get('disable_features') 120 if disable_features_data: 121 experiment['disable_features'] = disable_features_data 122 override_ui_strings = experiment_data.get('override_ui_strings') 123 if override_ui_strings: 124 experiment['override_ui_string'] = _ConvertOverrideUIStrings( 125 override_ui_strings) 126 return experiment 127 128def _CreateTrial(study_name, experiment_configs, platforms): 129 """Returns the applicable experiments for |study_name| and |platforms|. 130 131 This iterates through all of the experiment_configs for |study_name| 132 and picks out the applicable experiments based off of the valid platforms 133 and device type settings if specified. 134 """ 135 experiments = [] 136 for config in experiment_configs: 137 platform_intersection = [p for p in platforms if p in config['platforms']] 138 139 if platform_intersection: 140 experiments += [_CreateExperiment( 141 e, 142 platform_intersection, 143 config.get('form_factors', []), 144 config.get('is_low_end_device')) 145 for e in config['experiments']] 146 return { 147 'name': study_name, 148 'experiments': experiments, 149 } 150 151def _GenerateTrials(config, platforms): 152 for study_name in sorted(config.keys()): 153 study = _CreateTrial(study_name, config[study_name], platforms) 154 # To avoid converting studies with empty experiments (e.g. the study doesn't 155 # apply to the target platforms), this generator only yields studies that 156 # have non-empty experiments. 157 if study['experiments']: 158 yield study 159 160def ConfigToStudies(config, platforms): 161 """Returns the applicable studies from config for the platforms.""" 162 return [study for study in _GenerateTrials(config, platforms)] 163 164def _FieldTrialConfigToDescription(config, platforms): 165 return { 166 'elements': { 167 'kFieldTrialConfig': { 168 'studies': ConfigToStudies(config, platforms) 169 } 170 } 171 } 172 173def main(arguments): 174 parser = optparse.OptionParser( 175 description='Generates a struct from a JSON description.', 176 usage='usage: %prog [option] -s schema -p platform description') 177 parser.add_option('-b', '--destbase', 178 help='base directory of generated files.') 179 parser.add_option('-d', '--destdir', 180 help='directory to output generated files, relative to destbase.') 181 parser.add_option('-n', '--namespace', 182 help='C++ namespace for generated files. e.g search_providers.') 183 parser.add_option('-p', '--platform', action='append', choices=_platforms, 184 help='target platform for the field trial, mandatory.') 185 parser.add_option('-s', '--schema', help='path to the schema file, ' 186 'mandatory.') 187 parser.add_option('-o', '--output', help='output filename, ' 188 'mandatory.') 189 parser.add_option('-y', '--year', 190 help='year to put in the copy-right.') 191 (opts, args) = parser.parse_args(args=arguments) 192 193 if not opts.schema: 194 parser.error('You must specify a --schema.') 195 196 if not opts.platform: 197 parser.error('You must specify at least 1 --platform.') 198 199 description_filename = os.path.normpath(args[0]) 200 shortroot = opts.output 201 if opts.destdir: 202 output_root = os.path.join(os.path.normpath(opts.destdir), shortroot) 203 else: 204 output_root = shortroot 205 206 if opts.destbase: 207 basepath = os.path.normpath(opts.destbase) 208 else: 209 basepath = '' 210 211 schema = _Load(opts.schema) 212 description = _LoadFieldTrialConfig(description_filename, opts.platform) 213 json_to_struct.GenerateStruct( 214 basepath, output_root, opts.namespace, schema, description, 215 os.path.split(description_filename)[1], os.path.split(opts.schema)[1], 216 opts.year) 217 218if __name__ == '__main__': 219 main(sys.argv[1:]) 220