1#!/bin/python3
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, str):
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.items():
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", encoding="utf-8") as mk:
77        m.update(mk.read().encode("utf-8"))
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    )
90    make.append("set(%s_src %s)" % (name, " ".join(module["src"])))
91    if module["type"] == "SHARED_LIBRARY":
92        make.append(
93            "android_add_library(TARGET {} SHARED LICENSE Apache-2.0 SRC {})".format(
94                name, " ".join(module["src"])
95            )
96        )
97    elif module["type"] == "STATIC_LIBRARY":
98        make.append(
99            "android_add_library(TARGET {} LICENSE Apache-2.0 SRC {})".format(
100                name, " ".join(module["src"])
101            )
102        )
103    else:
104        raise ValueError("Unexpected module type: %s" % module["type"])
105
106    # Fix up the includes.
107    includes = ["${GOLDFISH_DEVICE_ROOT}/" + s for s in module["includes"]]
108    make.append(
109        "target_include_directories(%s PRIVATE %s)" % (name, " ".join(includes))
110    )
111
112    # filter out definitions
113    defs = [escape(d) for d in module["cflags"] if d.startswith("-D")]
114
115    #  And the remaining flags.
116    flags = [escape(d) for d in module["cflags"] if not d.startswith("-D")]
117
118    # Make sure we remove the lib prefix from all our dependencies.
119    libs = [remove_lib_prefix(l) for l in module.get("libs", [])]
120    staticlibs = [
121        remove_lib_prefix(l)
122        for l in module.get("staticlibs", [])
123        if l != "libandroidemu"
124    ]
125
126    # Configure the target.
127    make.append("target_compile_definitions(%s PRIVATE %s)" % (name, " ".join(defs)))
128    make.append("target_compile_options(%s PRIVATE %s)" % (name, " ".join(flags)))
129
130    if len(staticlibs) > 0:
131        make.append(
132            "target_link_libraries(%s PRIVATE %s PRIVATE %s)"
133            % (name, " ".join(libs), " ".join(staticlibs))
134        )
135    else:
136        make.append("target_link_libraries(%s PRIVATE %s)" % (name, " ".join(libs)))
137    return make
138
139
140def main(argv=None):
141    parser = argparse.ArgumentParser(
142        description="Generates a set of cmake files"
143        "based up the js representation."
144        "Use this to generate cmake files that can be consumed by the emulator build"
145    )
146    parser.add_argument(
147        "-i",
148        "--input",
149        dest="input",
150        type=str,
151        required=True,
152        help="json file containing the build tree",
153    )
154    parser.add_argument(
155        "-v",
156        "--verbose",
157        action="store_const",
158        dest="loglevel",
159        const=logging.INFO,
160        default=logging.ERROR,
161        help="Log what is happening",
162    )
163    parser.add_argument(
164        "-o",
165        "--output",
166        dest="outdir",
167        type=str,
168        default=None,
169        help="Output directory for create CMakefile.txt",
170    )
171    parser.add_argument(
172        "-c",
173        "--clean",
174        dest="output",
175        type=str,
176        default=None,
177        help="Write out the cleaned up js",
178    )
179    args = parser.parse_args()
180
181    logging.basicConfig(level=args.loglevel)
182
183    with open(args.input) as data_file:
184        data = json.load(data_file)
185
186    modules = cleanup_json(data)
187
188    # Write out cleaned up json, mainly useful for debugging etc.
189    if args.output is not None:
190        with open(args.output, "w") as out_file:
191            out_file.write(json.dumps(modules, indent=2))
192
193    # Location --> CMakeLists.txt
194    cmake = {}
195
196    # The root, it will basically just include all the generated files.
197    root = os.path.join(args.outdir, "CMakeLists.txt")
198    mkfile = os.path.join(args.outdir, "Android.mk")
199    sha256 = checksum(mkfile)
200    cmake[root] = header()
201    cmake[root].append("set(GOLDFISH_DEVICE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})")
202    cmake[root].append(
203        'android_validate_sha256("${GOLDFISH_DEVICE_ROOT}/%s" "%s")' % (mkfile, sha256)
204    )
205
206    # Generate the modules.
207    for module in modules:
208        location = os.path.join(args.outdir, module["path"], "CMakeLists.txt")
209
210        # Make sure we handle the case where we have >2 modules in the same dir.
211        if location not in cmake:
212            path = module["path"]
213            if path.startswith("../"):
214                path_binary_dir = path
215                path_binary_dir = path_binary_dir.replace("../", "")
216                path_binary_dir = path_binary_dir.replace("/", "-")
217                path_binary_dir = path_binary_dir.lower()
218
219                cmake[root].append("add_subdirectory(%s %s)" % (path, path_binary_dir))
220            else:
221                cmake[root].append("add_subdirectory(%s)" % path)
222            cmake[location] = []
223        cmake[location].extend(generate_module(module))
224
225    # Write them to disk.
226    for (loc, cmklist) in cmake.items():
227        logging.info("Writing to %s", loc)
228        with open(loc, "w") as fn:
229            fn.write("\n".join(cmklist))
230
231
232if __name__ == "__main__":
233    sys.exit(main())
234