xref: /aosp_15_r20/external/mesa3d/meson_to_hermetic/meson_impl.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1from enum import Enum
2from abc import ABC, abstractmethod
3import os
4import re
5import subprocess
6import tomllib
7
8# The file used to write output build definitions.
9_gOutputFile = ''
10
11# The relative directory that is currently being processed. When files are
12# referenced they are relative to this path.
13_gRelativeDir = ''
14
15# Global compiler flags
16_gProjectCflags = []
17_gProjectCppflags = []
18
19_gProjectVersion = 'unknown'
20_gProjectOptions = []
21
22# Caches the list of dependencies found in .toml config files
23# Structure:
24# DependencyTargetType
25#   SHARED_LIBRARY = 1
26#   STATIC_LIBRARY = 2
27#   HEADER_LIBRARY = 3
28# See meson_impl.py
29# external_dep = {
30#   'zlib': {
31#       # target_name: target_type
32#       'libz': 2
33#   },
34# }
35external_dep = {}
36
37
38class IncludeDirectories:
39    def __init__(self, name: str, dirs: []):
40        self.name = name
41        self.dirs = dirs
42
43    def __iter__(self):
44        return iter([self])
45
46
47class File:
48    def __init__(self, name: str):
49        self.name = name
50
51
52class Machine:
53    def __init__(self, system, cpu, cpu_family):
54        self._system = system
55        self._cpu = cpu
56        self._cpu_family = cpu_family
57
58    def system(self):
59        return self._system
60
61    def set_system(self, system: str):
62        self._system = system
63
64    def cpu_family(self):
65        return self._cpu_family
66
67    def cpu(self):
68        return self._cpu
69
70
71class DependencyTargetType(Enum):
72    SHARED_LIBRARY = 1
73    STATIC_LIBRARY = 2
74    HEADER_LIBRARY = 3
75
76
77class DependencyTarget:
78    def __init__(self, target_name: str, target_type: DependencyTargetType):
79        self.target_name = target_name
80        self.target_type = target_type
81
82
83class Dependency:
84    _id_generator = 1000
85
86    def __init__(
87        self,
88        name: str,
89        version='',
90        found=False,
91        targets=[],
92        compile_args=[],
93        include_directories=[],
94        dependencies=[],
95        sources=[],
96        link_with=[],
97        link_whole=[],
98    ):
99        self.name = name
100        self.targets = targets
101        self._version = version
102        self._found = found
103        self.compile_args = compile_args
104        self.include_directories = include_directories
105        self.dependencies = dependencies
106        self.sources = sources
107        self.link_with = link_with
108        self.link_whole = link_whole
109        Dependency._id_generator += 1
110        self.unique_id = Dependency._id_generator
111
112    def version(self):
113        return self._version
114
115    def found(self):
116        return self._found
117
118    def partial_dependency(self, compile_args=''):
119        return self
120
121    def __iter__(self):
122        return iter([self])
123
124    def __hash__(self):
125        return hash(self.unique_id)
126
127    def __eq__(self, other):
128        return self.unique_id == other.unique_id
129
130
131class CommandReturn:
132    def __init__(self, completed_process):
133        self.completed_process = completed_process
134
135    def returncode(self):
136        return self.completed_process.returncode
137
138    def stdout(self):
139        return self.completed_process.stdout
140
141
142class Program:
143    def __init__(self, command, found: bool):
144        self.command = command
145        self._found = found
146
147    # Running commands from the ambient system may give wrong/misleading results, since
148    # some build systems use hermetic installations of tools like python.
149    def run_command(self, *commands, capture_output=False):
150        command_line = [self.command]
151        for command in commands:
152            command_line += command
153
154        completed_process = subprocess.run(
155            command_line, check=False, capture_output=capture_output
156        )
157        return CommandReturn(completed_process)
158
159    def found(self):
160        return self._found
161
162    def full_path(self):
163        return 'full_path'
164
165
166class PythonModule:
167    def find_installation(self, name: str):
168        if name == 'python3':
169            return Program(name, found=True)
170        exit('Unhandled python installation: ' + name)
171
172
173class EnableState(Enum):
174    ENABLED = 1
175    DISABLED = 2
176    AUTO = 3
177
178
179class FeatureOption:
180    def __init__(self, name, state=EnableState.AUTO):
181        self.name = name
182        self.state = state
183
184    def allowed(self):
185        return self.state == EnableState.ENABLED or self.state == EnableState.AUTO
186
187    def enabled(self):
188        return self.state == EnableState.ENABLED
189
190    def disabled(self):
191        return self.state == EnableState.DISABLED
192
193    def disable_auto_if(self, value: bool):
194        if value and self.state == EnableState.AUTO:
195            self.state = EnableState.DISABLED
196        return self
197
198    def disable_if(self, value: bool, error_message: str):
199        if not value:
200            return self
201        if self.state == EnableState.ENABLED:
202            exit(error_message)
203        return FeatureOption(self.name, state=EnableState.DISABLED)
204
205    def require(self, value: bool, error_message: str):
206        if value:
207            return self
208        if self.state == EnableState.ENABLED:
209            exit(error_message)
210        return FeatureOption(self.name, state=EnableState.DISABLED)
211
212    def set(self, value: str):
213        value = value.lower()
214        if value == 'auto':
215            self.state = EnableState.AUTO
216        elif value == 'enabled':
217            self.state = EnableState.ENABLED
218        elif value == 'disabled':
219            self.state = EnableState.DISABLED
220        else:
221            exit('Unable to set feature to: %s' % value)
222
223
224class ArrayOption:
225    def __init__(self, name: str, value: []):
226        self.name = name
227        self.strings = value
228
229    def set(self, value: str):
230        if value == '':
231            self.strings = []
232        else:
233            self.strings = [value]
234
235
236class ComboOption:
237    def __init__(self, name: str, value: str):
238        self.name = name
239        self.value = value
240
241    def set(self, value: str):
242        self.value = value
243
244
245class BooleanOption:
246    def __init__(self, name, value: bool):
247        assert type(value) is bool
248        self.name = name
249        self.value = value
250
251    def set(self, value: str):
252        self.value = bool(value)
253
254
255# Value can be string or other type
256class SimpleOption:
257    def __init__(self, name, value):
258        self.name = name
259        self.value = value
260
261    def set(self, value: str):
262        if type(self.value) is int:
263            self.value = int(value)
264        else:
265            self.value = value
266
267
268class Environment:
269    def set(self, var, val):
270        return
271
272    def append(self, var, val):
273        return
274
275
276class StaticLibrary:
277    def __init__(self, target_name, link_with=[], link_whole=[]):
278        self.target_name = target_name
279        self.link_with = link_with
280        self.link_whole = link_whole
281
282
283class SharedLibrary:
284    name = ''
285
286    def __init__(self, name):
287        self.name = name
288
289
290class Executable:
291    def __init__(self, name):
292        self.name = name
293
294
295class CustomTargetItem:
296    def __init__(self, custom_target, index):
297        self.target = custom_target
298        self.index = index
299
300
301class CustomTarget:
302    def __init__(self, name, outputs=[], generates_h=False, generates_c=False):
303        self._name = name
304        self._outputs = outputs
305        self._generates_h = generates_h
306        self._generates_c = generates_c
307
308    @property
309    def outputs(self):
310        return self._outputs
311
312    def generates_h(self):
313        return self._generates_h
314
315    def generates_c(self):
316        return self._generates_c
317
318    def target_name(self):
319        return self._name
320
321    def target_name_h(self):
322        if self._generates_h and self._generates_c:
323            return self._name + '.h'
324        return self._name
325
326    def target_name_c(self):
327        if self._generates_h and self._generates_c:
328            return self._name + '.c'
329        return self._name
330
331    def header_outputs(self):
332        hdrs = []
333        for out in self._outputs:
334            if out.endswith('.h'):
335                hdrs.append(out)
336        return hdrs
337
338    def __iter__(self):
339        return iter([self])
340
341    def __getitem__(self, index):
342        return CustomTargetItem(self, index)
343
344    def full_path(self):
345        return 'fullpath'
346
347
348class Meson:
349    def __init__(self, compiler):
350        self._compiler = compiler
351
352    def get_compiler(self, language_string, native=False):
353        return self._compiler
354
355    def set_compiler(self, compiler):
356        self._compiler = compiler
357
358    def project_version(self):
359        return _gProjectVersion
360
361    def project_source_root(self):
362        return os.getcwd()
363
364    def is_cross_build(self):
365        return True
366
367    def can_run_host_binaries(self):
368        return False
369
370    def current_source_dir(self):
371        return os.getcwd()
372
373    def current_build_dir(self):
374        return '@CURRENT_BUILD_DIR@'
375
376    def project_build_root(self):
377        return '@PROJECT_BUILD_ROOT@'
378
379    def add_devenv(self, env):
380        return
381
382
383class Compiler(ABC):
384    def __init__(self, cpu_family):
385        self._id = 'clang'
386        self._cpu_family = cpu_family
387
388    @abstractmethod
389    def has_header_symbol(
390        self,
391        header: str,
392        symbol: str,
393        args=None,
394        dependencies=None,
395        include_directories=None,
396        no_builtin_args: bool = False,
397        prefix=None,
398        required: bool = False,
399    ) -> bool:
400        pass
401
402    @abstractmethod
403    def check_header(self, header: str, prefix: str = '') -> bool:
404        pass
405
406    @abstractmethod
407    def has_function(self, function, args=None, prefix='', dependencies='') -> bool:
408        pass
409
410    @abstractmethod
411    def links(self, snippet: str, name: str, args=None, dependencies=None) -> bool:
412        pass
413
414    def get_id(self):
415        return self._id
416
417    def is_symbol_supported(self, header: str, symbol: str):
418        if header == 'sys/mkdev.h' or symbol == 'program_invocation_name':
419            return False
420        return True
421
422    def is_function_supported(self, function: str):
423        if (
424            function == 'qsort_s'
425            or function == 'pthread_setaffinity_np'
426            or function == 'secure_getenv'
427        ):
428            return False
429        return True
430
431    def is_link_supported(self, name: str):
432        if name == 'GNU qsort_r' or name == 'BSD qsort_r':
433            return False
434        return True
435
436    def is_header_supported(self, header: str):
437        if (
438            header == 'xlocale.h'
439            or header == 'pthread_np.h'
440            or header == 'renderdoc_app.h'
441        ):
442            return False
443        return True
444
445    def get_define(self, define: str, prefix: str):
446        if define == 'ETIME':
447            return define
448        exit('Unhandled define: ' + define)
449
450    def get_supported_function_attributes(self, attributes: list[str]):
451        # Assume all are supported
452        return attributes
453
454    def has_function_attribute(self, attribute: str):
455        return True
456
457    def has_argument(self, name: str):
458        result = True
459        print("has_argument '%s': %s" % (name, str(result)))
460        return result
461
462    def has_link_argument(self, name: str):
463        result = True
464        print("has_link_argument '%s': %s" % (name, str(result)))
465        return result
466
467    def compiles(self, snippet, name: str):
468        # Exclude what is currently not working.
469        result = True
470        if name == '__uint128_t':
471            result = False
472        print("compiles '%s': %s" % (name, str(result)))
473        return result
474
475    def has_member(self, struct, member, prefix):
476        # Assume it does
477        return True
478
479    def get_argument_syntax(self):
480        return 'gcc'
481
482    def get_supported_arguments(self, args):
483        supported_args = []
484        for arg in args:
485            if (
486                arg.startswith('-flifetime-dse')
487                or arg.startswith('-Wno-format-truncation')
488                or arg.startswith('-Wno-nonnull-compare')
489                or arg.startswith('-Wno-class-memaccess')
490                or arg.startswith('-Wno-format-truncation')
491            ):
492                continue
493            supported_args.append(arg)
494        return supported_args
495
496    def get_supported_link_arguments(self, args):
497        return args
498
499    def find_library(self, name, required=False):
500        if name == 'ws2_32' or name == 'elf' or name == 'm' or name == 'sensors':
501            return Dependency(name, found=required)
502        exit('Unhandled library: ' + name)
503
504    def sizeof(self, string):
505        table = _get_sizeof_table(self._cpu_family)
506
507        if string not in table:
508            exit('Unhandled compiler sizeof: ' + string)
509        return table[string]
510
511
512class PkgConfigModule:
513    def generate(
514        self,
515        lib,
516        name='',
517        description='',
518        extra_cflags=None,
519        filebase='',
520        version='',
521        libraries=None,
522        libraries_private=None,
523    ):
524        pass
525
526
527###################################################################################################
528
529
530def fprint(args):
531    print(args, file=_gOutputFile)
532
533
534def set_relative_dir(dir):
535    global _gRelativeDir
536    _gRelativeDir = dir
537
538
539def open_output_file(name):
540    global _gOutputFile
541    _gOutputFile = open(name, 'w')
542
543
544def close_output_file():
545    global _gOutputFile
546    _gOutputFile.close()
547
548
549def get_relative_dir(path_or_file=''):
550    if isinstance(path_or_file, File):
551        return path_or_file.name
552
553    assert isinstance(path_or_file, str)
554    if path_or_file == '':
555        return _gRelativeDir
556    return os.path.join(_gRelativeDir, path_or_file)
557
558
559def get_relative_gen_dir(path=''):
560    return os.path.join(_gRelativeDir, path)
561
562
563def project(name, language_list, version, license, meson_version, default_options):
564    if type(version) is str:
565        _gProjectVersion = version
566    else:
567        assert type(version) is list
568        version_file = version[0]
569        assert type(version_file) is File
570        with open(version_file.name, 'r') as file:
571            for line in file:
572                _gProjectVersion = line.strip()
573                break
574
575    for option in default_options:
576        value_pair = option.split('=')
577        _gProjectOptions.append(SimpleOption(value_pair[0], value_pair[1]))
578
579
580def get_project_options():
581    return _gProjectOptions
582
583
584def add_project_arguments(args, language=[], native=False):
585    global _gProjectCflags, _gProjectCppflags
586    if type(args) is not list:
587        args = [args]
588    for lang in language:
589        for arg in args:
590            if isinstance(arg, list):
591                add_project_arguments(arg, language=language, native=native)
592                continue
593            assert isinstance(arg, str)
594            if lang == 'c':
595                print('cflags: ' + arg)
596                _gProjectCflags.append(arg)
597            elif lang == 'cpp':
598                print('cppflags: ' + arg)
599                _gProjectCppflags.append(arg)
600            else:
601                exit('Unhandle arguments language: ' + lang)
602
603
604def get_project_cflags():
605    return _gProjectCflags
606
607
608def get_project_cppflags():
609    return _gProjectCppflags
610
611
612def _get_sizeof_table(cpu_family):
613    table_32 = {'void*': 4}
614    table_64 = {'void*': 8}
615    if cpu_family == 'arm' or cpu_family == 'x86_64':
616        table = table_32
617    elif cpu_family == 'aarch64':
618        table = table_64
619    else:
620        exit('sizeof unhandled cpu family: %s' % cpu_family)
621    return table
622
623
624def get_linear_list(arg_list):
625    args = []
626    for arg in arg_list:
627        if type(arg) is list:
628            args.extend(get_linear_list(arg))
629        else:
630            args.append(arg)
631    return args
632
633
634def load_dependencies(config):
635    with open(config, 'rb') as f:
636        data = tomllib.load(f)
637        project_configs = data.get('project_config')
638        base_config = data.get('base_project_config')
639        # global dependencies
640        for dep_name, targets in base_config.get('ext_dependencies').items():
641            dep_targets = {
642                t.get('target_name'): t.get('target_type') for t in targets
643            }
644            external_dep[dep_name] = dep_targets
645        # project specific dependencies
646        for project_config in project_configs:
647            dependencies = project_config.get('ext_dependencies')
648            for dep_name, targets in dependencies.items():
649                dep_targets = {
650                    t.get('target_name'): t.get('target_type') for t in targets
651                }
652                external_dep[dep_name] = dep_targets
653
654
655def dependency(*names, required=True, version=''):
656    for name in names:
657        print('dependency: %s' % name)
658        if name == '':
659            return Dependency('null', version, found=False)
660
661        if name in external_dep:
662            targets = external_dep.get(name)
663            return Dependency(
664                name,
665                targets=[
666                    DependencyTarget(t, DependencyTargetType(targets[t]))
667                    for t in targets
668                ],
669                version=version,
670                found=True,
671            )
672        # TODO(bpnguyen): Move these hardcoded dependencies to a global config
673        if (
674            name == 'backtrace'
675            or name == 'curses'
676            or name == 'expat'
677            or name == 'libconfig'
678            or name == 'libmagma_virt'
679            or name == 'libva'
680            or name == 'libzstd'
681            or name == 'libdrm'
682            or name == 'libglvnd'
683            or name == 'libudev'
684            or name == 'libunwind'
685            or name == 'llvm'
686            or name == 'libxml-2.0'
687            or name == 'lua54'
688            or name == 'valgrind'
689            or name == 'wayland-scanner'
690            or name == 'SPIRV-Tools'
691        ):
692            return Dependency(name, version, found=False)
693
694        if (
695            name == 'libarchive'
696            or name == 'libelf'
697            or name == 'threads'
698            or name == 'vdpau'
699        ):
700            return Dependency(name, version, found=required)
701
702        exit('Unhandled dependency: ' + name)
703
704
705def get_set_of_deps(deps, set_of_deps=set()):
706    for dep in deps:
707        if type(dep) is list:
708            set_of_deps = get_set_of_deps(dep, set_of_deps)
709        elif dep not in set_of_deps:
710            set_of_deps.add(dep)
711            set_of_deps = get_set_of_deps(dep.dependencies, set_of_deps)
712    return set_of_deps
713
714
715def get_include_dirs(paths) -> list[str]:
716    dir_list = []
717    for path in paths:
718        if type(path) is list:
719            dir_list.extend(get_include_dirs(p for p in path))
720        elif type(path) is IncludeDirectories:
721            dir_list.extend(path.dirs)
722        else:
723            assert type(path) is str
724            dir_list.append(get_relative_dir(path))
725    return dir_list
726
727
728def get_include_directories(includes) -> list[IncludeDirectories]:
729    dirs = []
730    if type(includes) is list:
731        for inc in includes:
732            dirs.extend(get_include_directories(inc))
733    elif type(includes) is IncludeDirectories:
734        dirs.extend(includes)
735    else:
736        assert type(includes) is str
737        exit('get_include_directories got string: %s' % includes)
738    return dirs
739
740
741def get_static_libs(arg_list):
742    libs = []
743    for arg in arg_list:
744        if type(arg) is list:
745            libs.extend(get_static_libs(arg))
746        else:
747            assert type(arg) is StaticLibrary
748            libs.extend(get_static_libs(arg.link_with))
749            libs.append(arg)
750    return libs
751
752
753def get_whole_static_libs(arg_list):
754    libs = []
755    for arg in arg_list:
756        if type(arg) is list:
757            libs.extend(get_whole_static_libs(arg))
758        else:
759            assert type(arg) is StaticLibrary
760            libs.extend(get_whole_static_libs(arg._link_whole))
761            libs.append(arg)
762    return libs
763
764
765def get_list_of_relative_inputs(list_or_string):
766    if isinstance(list_or_string, list):
767        ret = []
768        for item in list_or_string:
769            ret.extend(get_list_of_relative_inputs(item))
770        return ret
771
772    return [get_relative_dir(list_or_string)]
773
774
775def get_command_line_from_args(args: list):
776    command_line = ''
777    for arg in args:
778        command_line += ' ' + arg
779    # Escape angle brackets
780    command_line = re.sub(r'(<|>)', '\\\\\\\\\g<1>', command_line)
781    return command_line
782
783
784def replace_wrapped_input_with_target(args, python_script, python_script_target_name):
785    outargs = []
786    for index, arg in enumerate(args):
787        pattern = '(.*?)(' + python_script + ')'
788        replace = '\g<1>' + python_script_target_name
789        outargs.append(re.sub(pattern, replace, arg))
790    return outargs
791