1#!/bin/python
2import argparse
3import hashlib
4import json
5import logging
6import os
7import sys
8
9
10def cleanup_json(data):
11    """Cleans up the json structure by removing empty "", and empty key value
12    pairs."""
13    if (isinstance(data, unicode)):
14        copy = data.strip()
15        return None if len(copy) == 0 else copy
16
17    if (isinstance(data, dict)):
18        copy = {}
19        for key, value in data.iteritems():
20            rem = cleanup_json(value)
21            if (rem is not None):
22                copy[key] = rem
23        return None if len(copy) == 0 else copy
24
25    if (isinstance(data, list)):
26        copy = []
27        for elem in data:
28            rem = cleanup_json(elem)
29            if (rem is not None):
30                if rem not in copy:
31                    copy.append(rem)
32
33        if len(copy) == 0:
34            return None
35        return copy
36
37
38class AttrDict(dict):
39    def __init__(self, *args, **kwargs):
40        super(AttrDict, self).__init__(*args, **kwargs)
41        self.__dict__ = self
42
43    def as_list(self, name):
44        v = self.get(name, [])
45        if (isinstance(v, list)):
46            return v
47
48        return [v]
49
50
51def remove_lib_prefix(module):
52    """Removes the lib prefix, as we are not using them in CMake."""
53    if module.startswith('lib'):
54        return module[3:]
55    else:
56        return module
57
58
59def escape(msg):
60    """Escapes the "."""
61    return '"' + msg.replace('"', '\\"') + '"'
62
63
64def header():
65    """The auto generate header."""
66    return [
67        '# This is an autogenerated file! Do not edit!',
68        '# instead run make from .../device/generic/goldfish-opengl',
69        '# which will re-generate this file.'
70    ]
71
72
73def checksum(fname):
74    """Calculates a SHA256 digest of the given file name."""
75    m = hashlib.sha256()
76    with open(fname, 'r') as mk:
77        m.update(mk.read())
78    return m.hexdigest()
79
80
81def generate_module(module):
82    """Generates a cmake module."""
83    name = remove_lib_prefix(module['module'])
84    make = header()
85    mkfile = os.path.join(module['path'], 'Android.mk')
86    sha256 = checksum(mkfile)
87    make.append(
88        'android_validate_sha256("${GOLDFISH_DEVICE_ROOT}/%s" "%s")' % (mkfile, sha256))
89    make.append('set(%s_src %s)' % (name, ' '.join(module['src'])))
90    if module['type'] == 'SHARED_LIBRARY':
91        make.append('android_add_library(TARGET {} SHARED LICENSE Apache-2.0 SRC {})'.format(name, ' '.join(module['src'])))
92    elif module['type'] == 'STATIC_LIBRARY':
93        make.append('android_add_library(TARGET {} LICENSE Apache-2.0 SRC {})'.format(name, ' '.join(module['src'])))
94    else:
95        raise ValueError('Unexpected module type: %s' % module['type'])
96
97    # Fix up the includes.
98    includes = ['${GOLDFISH_DEVICE_ROOT}/' + s for s in module['includes']]
99    make.append('target_include_directories(%s PRIVATE %s)' %
100                (name, ' '.join(includes)))
101
102    # filter out definitions
103    defs = [escape(d) for d in module['cflags'] if d.startswith('-D')]
104
105    #  And the remaining flags.
106    flags = [escape(d) for d in module['cflags'] if not d.startswith('-D')]
107
108    # Make sure we remove the lib prefix from all our dependencies.
109    libs = [remove_lib_prefix(l) for l in module.get('libs', [])]
110    staticlibs = [remove_lib_prefix(l) for l in
111                      module.get('staticlibs', [])
112                      if l != "libandroidemu"]
113
114    # Configure the target.
115    make.append('target_compile_definitions(%s PRIVATE %s)' %
116                (name, ' '.join(defs)))
117    make.append('target_compile_options(%s PRIVATE %s)' %
118                (name, ' '.join(flags)))
119
120    if len(staticlibs) > 0:
121        make.append('target_link_libraries(%s PRIVATE %s PRIVATE %s)' %
122                    (name, ' '.join(libs), " ".join(staticlibs)))
123    else:
124        make.append('target_link_libraries(%s PRIVATE %s)' %
125                    (name, ' '.join(libs)))
126    return make
127
128
129def main(argv=None):
130    parser = argparse.ArgumentParser(
131        description='Generates a set of cmake files'
132        'based up the js representation.'
133        'Use this to generate cmake files that can be consumed by the emulator build')
134    parser.add_argument('-i', '--input', dest='input', type=str, required=True,
135                        help='json file containing the build tree')
136    parser.add_argument('-v', '--verbose',
137                        action='store_const', dest='loglevel',
138                        const=logging.INFO, default=logging.ERROR,
139                        help='Log what is happening')
140    parser.add_argument('-o', '--output',
141                        dest='outdir', type=str, default=None,
142                        help='Output directory for create CMakefile.txt')
143    parser.add_argument('-c', '--clean', dest='output', type=str,
144                        default=None,
145                        help='Write out the cleaned up js')
146    args = parser.parse_args()
147
148    logging.basicConfig(level=args.loglevel)
149
150    with open(args.input) as data_file:
151        data = json.load(data_file)
152
153    modules = cleanup_json(data)
154
155    # Write out cleaned up json, mainly useful for debugging etc.
156    if (args.output is not None):
157        with open(args.output, 'w') as out_file:
158            out_file.write(json.dumps(modules, indent=2))
159
160    # Location --> CMakeLists.txt
161    cmake = {}
162
163    # The root, it will basically just include all the generated files.
164    root = os.path.join(args.outdir, 'CMakeLists.txt')
165    mkfile = os.path.join(args.outdir, 'Android.mk')
166    sha256 = checksum(mkfile)
167    cmake[root] = header()
168    cmake[root].append('set(GOLDFISH_DEVICE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})')
169    cmake[root].append(
170        'android_validate_sha256("${GOLDFISH_DEVICE_ROOT}/%s" "%s")' % (mkfile, sha256))
171
172    # Generate the modules.
173    for module in modules:
174        location = os.path.join(args.outdir, module['path'], 'CMakeLists.txt')
175
176        # Make sure we handle the case where we have >2 modules in the same dir.
177        if location not in cmake:
178            cmake[root].append('add_subdirectory(%s)' % module['path'])
179            cmake[location] = []
180        cmake[location].extend(generate_module(module))
181
182    # Write them to disk.
183    for (loc, cmklist) in cmake.iteritems():
184        logging.info('Writing to %s', loc)
185        with open(loc, 'w') as fn:
186            fn.write('\n'.join(cmklist))
187
188
189if __name__ == '__main__':
190    sys.exit(main())
191