blob: 9ecf38974795f65a67cca87596936333ec09e144 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2015 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 json
import os.path
import sys
import optparse
_script_path = os.path.realpath(__file__)
sys.path.insert(0, os.path.normpath(_script_path + "/../../json_comment_eater"))
try:
import json_comment_eater
finally:
sys.path.pop(0)
sys.path.insert(0, os.path.normpath(_script_path + "/../../json_to_struct"))
try:
import json_to_struct
finally:
sys.path.pop(0)
sys.path.insert(
0,
os.path.normpath(_script_path + "/../../../components/variations/service"))
try:
import generate_ui_string_overrider
finally:
sys.path.pop(0)
_platforms = [
'android',
'android_weblayer',
'android_webview',
'chromeos',
'chromeos_lacros',
'fuchsia',
'ios',
'linux',
'mac',
'windows',
]
_form_factors = [
'desktop',
'phone',
'tablet',
]
# Convert a platform argument to the matching Platform enum value in
# components/variations/proto/study.proto.
def _PlatformEnumValue(platform):
assert platform in _platforms
return 'Study::PLATFORM_' + platform.upper()
def _FormFactorEnumValue(form_factor):
assert form_factor in _form_factors
return 'Study::' + form_factor.upper()
def _Load(filename):
"""Loads a JSON file into a Python object and return this object."""
with open(filename, 'r') as handle:
result = json.loads(json_comment_eater.Nom(handle.read()))
return result
def _LoadFieldTrialConfig(filename, platforms, invert):
"""Loads a field trial config JSON and converts it into a format that can be
used by json_to_struct.
"""
return _FieldTrialConfigToDescription(_Load(filename), platforms, invert)
def _ConvertOverrideUIStrings(override_ui_strings):
"""Converts override_ui_strings to formatted dicts."""
overrides = []
for ui_string, override in override_ui_strings.iteritems():
overrides.append({
'name_hash': generate_ui_string_overrider.HashName(ui_string),
'value': override
})
return overrides
def _CreateExperiment(experiment_data,
platforms,
form_factors,
is_low_end_device,
invert=False):
"""Creates an experiment dictionary with all necessary information.
Args:
experiment_data: An experiment json config.
platforms: A list of platforms for this trial. This should be
a subset of |_platforms|.
form_factors: A list of form factors for this trial. This should be
a subset of |_form_factors|.
is_low_end_device: An optional parameter. This can either be True or
False. None if not specified.
invert: An optional parameter. If set, inverts the enabled and disabled
set of experiments. Controlled by a GN flag.
Returns:
An experiment dict.
"""
experiment = {
'name': experiment_data['name'],
'platforms': [_PlatformEnumValue(p) for p in platforms],
'form_factors': [_FormFactorEnumValue(f) for f in form_factors],
}
if is_low_end_device is not None:
experiment['is_low_end_device'] = str(is_low_end_device).lower()
forcing_flags_data = experiment_data.get('forcing_flag')
if forcing_flags_data:
experiment['forcing_flag'] = forcing_flags_data
min_os_version_data = experiment_data.get('min_os_version')
if min_os_version_data:
experiment['min_os_version'] = min_os_version_data
params_data = experiment_data.get('params')
if (params_data):
experiment['params'] = [{'key': param, 'value': params_data[param]}
for param in sorted(params_data.keys())];
enable_features_data = experiment_data.get('enable_features')
disable_features_data = experiment_data.get('disable_features')
if enable_features_data or (invert and disable_features_data):
experiment['enable_features'] = (disable_features_data
if invert else enable_features_data)
if disable_features_data or (invert and enable_features_data):
experiment['disable_features'] = (enable_features_data
if invert else disable_features_data)
override_ui_strings = experiment_data.get('override_ui_strings')
if override_ui_strings:
experiment['override_ui_string'] = _ConvertOverrideUIStrings(
override_ui_strings)
return experiment
def _CreateTrial(study_name, experiment_configs, platforms, invert):
"""Returns the applicable experiments for |study_name| and |platforms|.
This iterates through all of the experiment_configs for |study_name|
and picks out the applicable experiments based off of the valid platforms
and device type settings if specified.
"""
experiments = []
for config in experiment_configs:
platform_intersection = [p for p in platforms if p in config['platforms']]
if platform_intersection:
experiments += [
_CreateExperiment(e,
platform_intersection,
config.get('form_factors', []),
config.get('is_low_end_device'),
invert=invert) for e in config['experiments']
]
return {
'name': study_name,
'experiments': experiments,
}
def _GenerateTrials(config, platforms, invert):
for study_name in sorted(config.keys()):
study = _CreateTrial(study_name, config[study_name], platforms, invert)
# To avoid converting studies with empty experiments (e.g. the study doesn't
# apply to the target platforms), this generator only yields studies that
# have non-empty experiments.
if study['experiments']:
yield study
def ConfigToStudies(config, platforms, invert):
"""Returns the applicable studies from config for the platforms."""
return [study for study in _GenerateTrials(config, platforms, invert)]
def _FieldTrialConfigToDescription(config, platforms, invert):
return {
'elements': {
'kFieldTrialConfig': {
'studies': ConfigToStudies(config, platforms, invert)
}
}
}
def main(arguments):
parser = optparse.OptionParser(
description='Generates a struct from a JSON description.',
usage='usage: %prog [option] -s schema -p platform description')
parser.add_option('-b', '--destbase',
help='base directory of generated files.')
parser.add_option('-d', '--destdir',
help='directory to output generated files, relative to destbase.')
parser.add_option('-n', '--namespace',
help='C++ namespace for generated files. e.g search_providers.')
parser.add_option('-p', '--platform', action='append', choices=_platforms,
help='target platform for the field trial, mandatory.')
parser.add_option('-s', '--schema', help='path to the schema file, '
'mandatory.')
parser.add_option('-o', '--output', help='output filename, '
'mandatory.')
parser.add_option('-y', '--year',
help='year to put in the copy-right.')
parser.add_option(
'--invert_fieldtrials',
action='store_true',
help=
"Inverts the enabled and disabled experiments for existing field trials.")
(opts, args) = parser.parse_args(args=arguments)
if not opts.schema:
parser.error('You must specify a --schema.')
if not opts.platform:
parser.error('You must specify at least 1 --platform.')
description_filename = os.path.normpath(args[0])
shortroot = opts.output
if opts.destdir:
output_root = os.path.join(os.path.normpath(opts.destdir), shortroot)
else:
output_root = shortroot
if opts.destbase:
basepath = os.path.normpath(opts.destbase)
else:
basepath = ''
schema = _Load(opts.schema)
description = _LoadFieldTrialConfig(description_filename, opts.platform,
opts.invert_fieldtrials)
json_to_struct.GenerateStruct(
basepath, output_root, opts.namespace, schema, description,
os.path.split(description_filename)[1], os.path.split(opts.schema)[1],
opts.year)
if __name__ == '__main__':
main(sys.argv[1:])