blob: 171efb43b30dbf5b1f9f1b65937172afd2732ee9 [file] [log] [blame]
#!/usr/bin/python2.7
#
# Copyright 2013 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Convert a code_tally.exe json file to a json file that can be uploaded to
# the app engine size server.
import json
import optparse
import os
import sys
def _json_default(obj):
"""Handles JSON serializablity of Entity class."""
if isinstance(obj, Entity):
return obj.__dict__
raise TypeError(repr(obj) + ' is not JSON serializable')
class Entity(object):
"""An entity is a unit representing a single source file or folder that
contributed to the dll.
"""
def __init__(self, parent, name, size, is_directory=False):
self.parent = parent
self.name = name
self.size = size
self.is_directory = is_directory
def _normalize_paths(input_json):
"""Ensure that all paths are lower case (to prevent potential mis-matches,
since Windows is case-insensitive.
"""
normalized_json = input_json.copy()
for i in range(len(normalized_json['sources'])):
normalized_json['sources'][i] = normalized_json['sources'][i].lower()
return normalized_json
def _calculate_sizes(input_json):
"""Calculate the sizes of all the source files, returning a list of floats
representing the size in the same order as input_json['sources'].
"""
source_sizes = [0.0] * len(input_json['sources'])
for obj_name, value in input_json['objects'].iteritems():
for func_name, func_prop in value.iteritems():
# Even if we have the size property, without the contribs we are unable
# to properly assign the sizes.
if 'contribs' not in func_prop:
continue
contribs = func_prop['contribs']
# The contribs value is a list where all the even elements are the source
# file number, and the odd elements are the contributions for that file
# (of the form [line position, size, line position, size, etc...]).
contributors = contribs[::2]
sizes = contribs[1::2]
for contributor, size in zip(contributors, sizes):
# TODO: include the line positions in the converted data.
line_positions = size[::2]
line_contributions = size[1::2]
source_sizes[contributor] += sum(line_contributions)
return source_sizes
def _generate_source_tree(sources, sizes):
"""Generates a dict equivalent to the source tree. Each element is either a
file (so its value is its size) or a folder (so its value is a dictionary of
all the files or folders found inside it).
|sources| is a list of files to build the source tree out of, and |sizes|
has the size for every file in |sources|.
An example of a dict that might get returned:
{
'c': {
'file1': 18.0,
'folder': {'file2': 20}
}
}
"""
source_tree = {}
for filepath, size in zip(sources, sizes):
split_path = filepath.split('\\')
# Ensure that all the parent folders have been created.
parent = source_tree
for section in split_path[:-1]:
parent = parent.setdefault(section, {})
# Set the appropriate size for the file.
parent[split_path[-1]] = size
return source_tree
def _convert_subtree_to_entities(source_tree, parent):
"""Given a |source_tree| dictionary representing files and folders, rooted at
|parent|, return the list of entities that contains all the tree's elements.
An example of the |source_tree| input is:
{
'c': {
'file1': 18.0,
'folder': {'file2': 20}
}
}
"""
entities = []
for key, value in source_tree.iteritems():
if isinstance(value, float):
# A basic file entity.
entities.append(Entity(parent, name=key, size=value))
else:
new_parent = key if not parent else parent + '\\' + key
children = _convert_subtree_to_entities(value, new_parent)
# Find the size of this directory, based on the sizes of it children.
# Ignore any children's children, since they are already included in
# their parent's sum.
total_size = sum([child.size for child in children
if child.parent == new_parent])
entities.extend(children)
entities.append(
Entity(parent, name=key, size=total_size, is_directory=True))
return entities
def _generate_entities(input_json):
"""Convert the given input json data to a list of entities."""
normalized_json = _normalize_paths(input_json)
sizes = _calculate_sizes(normalized_json)
source_tree = _generate_source_tree(normalized_json['sources'], sizes)
return _convert_subtree_to_entities(source_tree, '')
def _output_converted_json(output_file, options, name, entities):
"""Store the list of entities in the given output file, also including the
appropriate general fields (such as builder name, builder number, etc).
"""
output_json = {
'master.id': options.master_id,
'buildername': options.builder_name,
'buildnumber': options.build_number,
'name': name,
'revision': options.revision,
'entities': entities,
}
print 'Saving converted file \'%s\'' % output_file
json.dump(output_json, output_file, indent=2, default=_json_default)
def main():
usage = "usage: %prog [options] input_code_tally_file output_json_file"
parser = optparse.OptionParser(usage=usage)
parser.add_option('--master_id', default='master',
help='The master of the machine that generated the code '
'tally.')
parser.add_option('--builder_name', default='builder',
help='The name of the builder that generated the code '
'tally.')
parser.add_option('--build_number', default='1',
help='The build number.')
parser.add_option('--revision', default='1',
help='The revision of the code this code tally refers to.')
(options, args) = parser.parse_args()
if len(args) != 2:
parser.error('Please list the input and output files.')
input_file = args[0]
output_file = args[1]
if not os.path.exists(input_file):
parser.error('The given input file, \'%s\', doesn\'t exist' % input_file)
with open(input_file, 'r') as f:
input_data = json.load(f)
entities = _generate_entities(input_data)
with open(output_file, 'w') as f:
_output_converted_json(f,
options,
input_data['executable']['name'],
entities)
if __name__ == '__main__':
sys.exit(main())