1import os
2import sys
3import argparse
4import shutil
5import tempfile
6import json
7import subprocess
8
9base_path = os.path.abspath(os.path.dirname(__file__))
10sys.path.insert(0, base_path)
11
12project_path = os.path.abspath(os.path.join(base_path, '..', '..'))
13docs_path = os.path.join(project_path, 'docs')
14sys.path.insert(0, docs_path)
15
16import create_fake_lib_c  # NOQA
17import pycparser_monkeypatch  # NOQA
18import pycparser  # NOQA
19
20DEVELOP = False
21
22
23temp_directory = tempfile.mkdtemp(suffix='.lvgl_json')
24
25
26def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_private, no_docstrings, *compiler_args):
27
28    pycparser_monkeypatch.FILTER_PRIVATE = filter_private
29
30    lvgl_path = project_path
31    lvgl_src_path = os.path.join(lvgl_path, 'src')
32    temp_lvgl = os.path.join(temp_directory, 'lvgl')
33    target_header_base_name = (
34        os.path.splitext(os.path.split(target_header)[-1])[0]
35    )
36
37    try:
38        os.mkdir(temp_lvgl)
39        shutil.copytree(lvgl_src_path, os.path.join(temp_lvgl, 'src'))
40        shutil.copyfile(os.path.join(lvgl_path, 'lvgl.h'), os.path.join(temp_lvgl, 'lvgl.h'))
41
42        pp_file = os.path.join(temp_directory, target_header_base_name + '.pp')
43
44        if lvgl_config_path is None:
45            lvgl_config_path = os.path.join(lvgl_path, 'lv_conf_template.h')
46
47            with open(lvgl_config_path, 'rb') as f:
48                data = f.read().decode('utf-8').split('\n')
49
50            for i, line in enumerate(data):
51                if line.startswith('#if 0'):
52                    data[i] = '#if 1'
53                else:
54                    for item in (
55                        'LV_USE_LOG',
56                        'LV_USE_OBJ_ID',
57                        'LV_USE_OBJ_ID_BUILTIN',
58                        'LV_USE_FLOAT',
59                        'LV_USE_BIDI',
60                        'LV_USE_LODEPNG',
61                        'LV_USE_LIBPNG',
62                        'LV_USE_BMP',
63                        'LV_USE_TJPGD',
64                        'LV_USE_LIBJPEG_TURBO',
65                        'LV_USE_GIF',
66                        'LV_BIN_DECODER_RAM_LOAD',
67                        'LV_USE_RLE',
68                        'LV_USE_QRCODE',
69                        'LV_USE_BARCODE',
70                        'LV_USE_TINY_TTF',
71                        'LV_USE_GRIDNAV',
72                        'LV_USE_FRAGMENT',
73                        'LV_USE_IMGFONT',
74                        'LV_USE_SNAPSHOT',
75                        'LV_USE_FREETYPE'
76                    ):
77                        if line.startswith(f'#define {item} '):
78                            data[i] = f'#define {item} 1'
79                            break
80
81            with open(os.path.join(temp_directory, 'lv_conf.h'), 'wb') as f:
82                f.write('\n'.join(data).encode('utf-8'))
83        else:
84            src = lvgl_config_path
85            dst = os.path.join(temp_directory, 'lv_conf.h')
86            shutil.copyfile(src, dst)
87
88        include_dirs = [temp_directory, project_path]
89
90        if sys.platform.startswith('win'):
91            import get_sdl2
92
93            try:
94                import pyMSVC  # NOQA
95            except ImportError:
96                sys.stderr.write(
97                    '\nThe pyMSVC library is missing, '
98                    'please run "pip install pyMSVC" to install it.\n'
99                )
100                sys.stderr.flush()
101                sys.exit(-500)
102
103            env = pyMSVC.setup_environment()  # NOQA
104            cpp_cmd = ['cl', '/std:c11', '/nologo', '/P']
105            output_pp = f'/Fi"{pp_file}"'
106            sdl2_include, _ = get_sdl2.get_sdl2(temp_directory)
107            include_dirs.append(sdl2_include)
108            include_path_env_key = 'INCLUDE'
109
110        elif sys.platform.startswith('darwin'):
111            include_path_env_key = 'C_INCLUDE_PATH'
112            cpp_cmd = [
113                'clang', '-std=c11', '-E', '-DINT32_MIN=0x80000000',
114            ]
115            output_pp = f' >> "{pp_file}"'
116        else:
117            include_path_env_key = 'C_INCLUDE_PATH'
118            cpp_cmd = [
119                'gcc', '-std=c11', '-E', '-Wno-incompatible-pointer-types',
120            ]
121            output_pp = f' >> "{pp_file}"'
122
123        fake_libc_path = create_fake_lib_c.run(temp_directory)
124
125        if include_path_env_key not in os.environ:
126            os.environ[include_path_env_key] = ''
127
128        os.environ[include_path_env_key] = (
129            f'{fake_libc_path}{os.pathsep}{os.environ[include_path_env_key]}'
130        )
131
132        if 'PATH' not in os.environ:
133            os.environ['PATH'] = ''
134
135        os.environ['PATH'] = (
136            f'{fake_libc_path}{os.pathsep}{os.environ["PATH"]}'
137        )
138
139        cpp_cmd.extend(compiler_args)
140        cpp_cmd.extend([
141            '-DLV_LVGL_H_INCLUDE_SIMPLE',
142            '-DLV_CONF_INCLUDE_SIMPLE',
143            '-DLV_USE_DEV_VERSION'
144        ])
145
146        cpp_cmd.extend(['-DPYCPARSER', f'"-I{fake_libc_path}"'])
147        cpp_cmd.extend([f'"-I{item}"' for item in include_dirs])
148        cpp_cmd.append(f'"{target_header}"')
149
150        if sys.platform.startswith('win'):
151            cpp_cmd.insert(len(cpp_cmd) - 2, output_pp)
152        else:
153            cpp_cmd.append(output_pp)
154
155        cpp_cmd = ' '.join(cpp_cmd)
156
157        p = subprocess.Popen(
158            cpp_cmd,
159            stdout=subprocess.PIPE,
160            stderr=subprocess.PIPE,
161            env=os.environ,
162            shell=True
163        )
164        out, err = p.communicate()
165        exit_code = p.returncode
166
167        if not os.path.exists(pp_file):
168            sys.stdout.write(out.decode('utf-8').strip() + '\n')
169            sys.stdout.write('EXIT CODE: ' + str(exit_code) + '\n')
170            sys.stderr.write(err.decode('utf-8').strip() + '\n')
171            sys.stdout.flush()
172            sys.stderr.flush()
173
174            raise RuntimeError('Unknown Failure')
175
176        with open(pp_file, 'r') as f:
177            pp_data = f.read()
178
179        cparser = pycparser.CParser()
180        ast = cparser.parse(pp_data, target_header)
181
182        ast.setup_docs(no_docstrings, temp_directory)
183
184        if not output_to_stdout and output_path is None:
185            if not DEVELOP:
186                shutil.rmtree(temp_directory)
187
188            return ast
189
190        elif output_to_stdout:
191            # stdout.reset()
192            print(json.dumps(ast.to_dict(), indent=4))
193        else:
194            if not os.path.exists(output_path):
195                os.makedirs(output_path)
196
197            output_path = os.path.join(output_path, target_header_base_name + '.json')
198
199            with open(output_path, 'w') as f:
200                f.write(json.dumps(ast.to_dict(), indent=4))
201
202    except Exception as err:
203        try:
204            print(cpp_cmd)  # NOQA
205            print()
206        except:  # NOQA
207            pass
208
209        for key, value in os.environ.items():
210            print(key + ':', value)
211
212        print()
213        import traceback
214
215        traceback.print_exc()
216        print()
217
218        exceptions = [
219            ArithmeticError,
220            AssertionError,
221            AttributeError,
222            EOFError,
223            FloatingPointError,
224            GeneratorExit,
225            ImportError,
226            IndentationError,
227            IndexError,
228            KeyError,
229            KeyboardInterrupt,
230            LookupError,
231            MemoryError,
232            NameError,
233            NotImplementedError,
234            OverflowError,
235            ReferenceError,
236            RuntimeError,
237            StopIteration,
238            SyntaxError,
239            TabError,
240            SystemExit,
241            TypeError,
242            UnboundLocalError,
243            UnicodeError,
244            UnicodeEncodeError,
245            UnicodeDecodeError,
246            UnicodeTranslateError,
247            ValueError,
248            ZeroDivisionError,
249            SystemError
250        ]
251
252        if isinstance(err, OSError):
253            error = err.errno
254        else:
255            if type(err) in exceptions:
256                error = ~exceptions.index(type(err))
257            else:
258                error = -100
259    else:
260        error = 0
261
262    if DEVELOP:
263        print('temporary file path:', temp_directory)
264    else:
265        shutil.rmtree(temp_directory)
266
267    sys.exit(error)
268
269
270if __name__ == '__main__':
271    parser = argparse.ArgumentParser('-')
272    parser.add_argument(
273        '--output-path',
274        dest='output_path',
275        help=(
276            'output directory for JSON file. If one is not '
277            'supplied then it will be output stdout'
278        ),
279        action='store',
280        default=None
281    )
282    parser.add_argument(
283        '--lvgl-config',
284        dest='lv_conf',
285        help=(
286            'path to lv_conf.h (including file name), if this is not set then '
287            'a config file will be generated that has everything turned on.'
288        ),
289        action='store',
290        default=None
291    )
292    parser.add_argument(
293        '--develop',
294        dest='develop',
295        help='this option leaves the temporary folder in place.',
296        action='store_true',
297    )
298    parser.add_argument(
299        "--target-header",
300        dest="target_header",
301        help=(
302            "path to a custom header file. When using this to supply a custom"
303            "header file you MUST insure that any LVGL includes are done so "
304            "they are relitive to the LVGL repository root folder.\n\n"
305            '#include "src/lvgl_private.h"\n\n'
306            "If you have includes to header files that are not LVGL then you "
307            "will need to add the include locations for those header files "
308            "when running this script. It is done using the same manner that "
309            "is used when calling a C compiler\n\n"
310            "You need to provide the absolute path to the header file when "
311            "using this feature."
312        ),
313        action="store",
314        default=os.path.join(temp_directory, "lvgl", "lvgl.h")
315    )
316    parser.add_argument(
317        '--filter-private',
318        dest='filter_private',
319        help='Internal Use',
320        action='store_true',
321    )
322    parser.add_argument(
323        '--no-docstrings',
324        dest='no_docstrings',
325        help='Internal Use',
326        action='store_true',
327    )
328
329    args, extra_args = parser.parse_known_args()
330
331    DEVELOP = args.develop
332
333    run(args.output_path, args.lv_conf, args.output_path is None, args.target_header, args.filter_private, args.no_docstrings, *extra_args)
334