• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""
2Copyright 2024 Google LLC
3SPDX-License-Identifier: MIT
4"""
5
6import os
7import meson_impl as impl
8import tomllib
9import re
10
11from jinja2 import Environment, FileSystemLoader
12from pathlib import Path
13
14from meson_build_state import *
15
16jinja_env = Environment(
17    loader=FileSystemLoader(Path(__file__).parent.resolve() / 'templates/')
18)
19
20# A map that holds the build-system to build file
21# Keep the keys lower-case for non-case sensitivity
22OUTPUT_FILES = {'soong': r'Android_res.bp', 'bazel': r'BUILD.bazel'}
23
24
25def generate_build_file(translator, build_type: str):
26    defaults_gen = jinja_env.get_template('defaults/all_defaults.txt')
27    defaults = defaults_gen.render()
28    # Write our manually defined defaults
29    with open(OUTPUT_FILES[build_type], 'w') as file:
30        if build_type == 'soong':
31            file.write(defaults)
32        path = build_type + '/'
33        # Render all static libraries
34        static_libs_template = jinja_env.get_template(path + 'static_library.txt')
35        for static_lib in translator.meson_state.static_libraries:
36            if static_lib.library_type is LibraryType.LibraryShared:
37                static_libs_template = jinja_env.get_template(
38                    path + 'shared_library.txt'
39                )
40            cc_lib = static_libs_template.render(
41                name=static_lib.name,
42                host_supported='false',  # TODO(bpnguyen): Fix hardcoded host_supported
43                srcs=static_lib.srcs,
44                hdrs=static_lib.generated_headers,
45                generated_headers=static_lib.generated_headers,
46                generated_sources=static_lib.generated_sources,
47                copts=static_lib.copts,
48                c_std_val=static_lib.cstd,
49                cpp_std_val=static_lib.cpp_std,
50                cflags=static_lib.conlyflags,
51                cppflags=static_lib.cppflags,
52                include_directories=static_lib.local_include_dirs,
53                system_include_directories=static_lib.system_include_dirs,
54                static_libs=static_lib.static_libs,
55                whole_static_libs=static_lib.whole_static_libs,
56                shared_libs=static_lib.shared_libs,
57                header_libs=static_lib.header_libs,
58                target_compatible_with=static_lib.target_compatible_with,
59                deps=static_lib.deps,
60            )
61            # Set the template back to static by default
62            if static_lib.library_type is LibraryType.LibraryShared:
63                static_libs_template = jinja_env.get_template(
64                    path + 'static_library.txt'
65                )
66            file.write(cc_lib)
67
68        # Render genrules / custom targets
69        custom_target_template = jinja_env.get_template(path + 'genrule.txt')
70        for custom_target in translator.meson_state.custom_targets:
71            genrule = custom_target_template.render(
72                name=custom_target.name,
73                srcs=custom_target.srcs,
74                outs=custom_target.out,
75                tools=custom_target.tools,
76                export=len(custom_target.export_include_dirs) > 0,
77                export_include_dirs=custom_target.export_include_dirs,
78                cmd=custom_target.cmd,
79            )
80            file.write(genrule)
81
82        # python binary hosts
83        py_binary_host_template = jinja_env.get_template(path + 'py_binary.txt')
84        for py_binary in translator.meson_state.custom_py_targets:
85            py_binary_render = py_binary_host_template.render(
86                name=py_binary.name,
87                main=py_binary.main,
88                srcs=py_binary.srcs,
89                imports=py_binary.imports,
90            )
91            file.write(py_binary_render)
92
93        include_dir_template = jinja_env.get_template(path + 'include_directories.txt')
94        for include_dir in translator.meson_state.include_dirs:
95            include_dir_render = include_dir_template.render(
96                name=include_dir.name,
97                hdrs=include_dir.dirs,
98            )
99            file.write(include_dir_render)
100
101
102class SoongGenerator(impl.Compiler):
103    def has_header_symbol(
104        self,
105        header: str,
106        symbol: str,
107        args=[],
108        dependencies=[],
109        include_directories=[],
110        no_builtin_args=False,
111        prefix=[],
112        required=False,
113    ):
114        # Exclude what is currently not working.
115        result = self.is_symbol_supported(header, symbol)
116        print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result)))
117        return result
118
119    def check_header(self, header, prefix=''):
120        result = self.is_header_supported(header)
121        print("check_header '%s': %s" % (header, str(result)))
122        return result
123
124    def has_function(self, function, args=[], prefix='', dependencies=''):
125        # Exclude what is currently not working.
126        result = self.is_function_supported(function)
127        print("has_function '%s': %s" % (function, str(result)))
128        return result
129
130    def links(self, snippet, name, args=[], dependencies=[]):
131        # Exclude what is currently not working.
132        result = self.is_link_supported(name)
133        print("links '%s': %s" % (name, str(result)))
134        return result
135
136    def generate(self, translator):
137        """
138        Generates a Soong valid build file
139        :return: None
140        """
141        # Render all the defaults to the file first.
142        generate_build_file(translator, 'soong')
143
144
145class BazelGenerator(impl.Compiler):
146    def is_symbol_supported(self, header: str, symbol: str):
147        if header in meson_translator.config.headers_not_supported:
148            return False
149        if symbol in meson_translator.config.symbols_not_supported:
150            return False
151        return super().is_symbol_supported(header, symbol)
152
153    def is_function_supported(self, function):
154        if function in meson_translator.config.functions_not_supported:
155            return False
156        return super().is_function_supported(function)
157
158    def is_link_supported(self, name):
159        if name in meson_translator.config.links_not_supported:
160            return False
161        return super().is_link_supported(name)
162
163    def has_header_symbol(
164        self,
165        header: str,
166        symbol: str,
167        args=None,
168        dependencies=None,
169        include_directories=None,
170        no_builtin_args=False,
171        prefix=None,
172        required=False,
173    ):
174        if args is None:
175            args = []
176        if dependencies is None:
177            dependencies = []
178        if include_directories is None:
179            include_directories = []
180        if prefix is None:
181            prefix = []
182        # Exclude what is currently not working.
183        result = self.is_symbol_supported(header, symbol)
184        print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result)))
185        return result
186
187    def check_header(self, header, prefix=''):
188        result = self.is_header_supported(header)
189        print(f"check_header '{header}': {result}")
190        return result
191
192    def has_function(self, function, args=[], prefix='', dependencies='') -> bool:
193        # Exclude what is currently not working.
194        result = self.is_function_supported(function)
195        print(f"has_function '{function}': {result}")
196        return result
197
198    def links(self, snippet, name, args=[], dependencies=[]):
199        # Exclude what is currently not working.
200        result = self.is_link_supported(name)
201        print(f"links '{name}': {result}")
202        return result
203
204    def generate(self, translator):
205        generate_build_file(translator, 'bazel')
206
207
208class BazelPkgConfigModule(impl.PkgConfigModule):
209    def generate(
210        self,
211        lib,
212        name='',
213        description='',
214        extra_cflags=None,
215        filebase='',
216        version='',
217        libraries=None,
218        libraries_private=None,
219    ):
220        assert type(lib) is impl.StaticLibrary
221        sl = StaticLibrary()
222        sl.name = name
223        sl.deps = lib.target_name
224        sl.system_include_dirs.append(".")
225        sl.visibility.append('//visibility:public')
226        meson_translator.meson_state.static_libraries.append(sl)
227
228
229
230class MesonTranslator:
231    """
232    Abstract class that defines all common attributes
233    between different build systems (Soong, Bazel, etc...)
234    """
235
236    def __init__(self):
237        self._generator = None
238        self._config_file = None
239        self._build = ''
240        # configs and meson_project_states are ordered with each other.
241        self._configs: list[ProjectConfig] = []
242        self._meson_project_states: list[MesonProjectState] = []
243        # TODO(357080225): Fix the use hardcoded state 0
244        self._state = 0  # index of self._configs
245
246    def init_data(self, config_file: str):
247        self._config_file: str = config_file
248        print('CONFIG:', self._config_file)
249        self._init_metadata()
250        _generator = (
251            SoongGenerator(self.config.cpu_family)
252            if self._build.lower() == 'soong'
253            else BazelGenerator(self.config.cpu_family)
254        )
255        self._generator: impl.Compiler = _generator
256        return self
257
258    @property
259    def config_file(self) -> Path:
260        return self._config_file
261
262    @property
263    def host_machine(self) -> str:
264        return self._configs[self._state].host_machine
265
266    @property
267    def build_machine(self) -> str:
268        return self._configs[self._state].build_machine
269
270    @property
271    def generator(self) -> impl.Compiler:
272        return self._generator
273
274    @property
275    def build(self) -> str:
276        return self._build
277
278    @property
279    def config(self) -> ProjectConfig:
280        return self._configs[self._state]
281
282    @property
283    def meson_state(self):
284        return self._meson_project_states[self._state]
285
286    def is_soong(self):
287        return self._build.lower() == 'soong'
288
289    def is_bazel(self):
290        return self._build.lower() == 'bazel'
291
292    def get_output_filename(self) -> str:
293        return OUTPUT_FILES[self._build.lower()]
294
295    def _init_metadata(self):
296        """
297        To be called after self._config_file has alredy been assigned.
298        Parses the given config files for common build attributes
299        Fills the list of all project configs
300        :return: None
301        """
302        with open(self._config_file, 'rb') as f:
303            data = tomllib.load(f)
304            self._build = data.get('build')
305            base_config = data.get('base_project_config')
306
307            configs = data.get('project_config')
308            for config in configs:
309                proj_config = ProjectConfig.create_project_config(self._build, **config)
310                self._configs.append(
311                    proj_config
312                )
313                self._meson_project_states.append(MesonProjectState())
314
315            new_configs = []
316            # Handle Inheritance
317            for config in self._configs:
318                # Parent config, that contains shared attributes
319                base_proj_config = ProjectConfig.create_project_config(self._build, **base_config)
320                if not config.inherits_from:
321                    new_configs.append(config)
322                    continue
323                if config.inherits_from == 'base_project_config':
324                    new_configs.append(
325                        base_proj_config.extend(config).deepcopy()
326                    )
327                else:
328                    for proj_config in self._configs:
329                        if config.inherits_from == proj_config.name:
330                            new_configs.append(
331                                proj_config.extend(config).deepcopy()
332                            )
333            self._configs = new_configs
334
335
336# Declares an empty attribute data class
337# metadata is allocated during Generators-runtime (I.E. generate_<host>_build.py)
338meson_translator = MesonTranslator()
339
340_gIncludeDirectories = {}
341
342
343def load_meson_data(config_file: str):
344    meson_translator.init_data(config_file)
345
346
347def open_output_file():
348    impl.open_output_file(meson_translator.get_output_filename())
349
350
351def close_output_file():
352    impl.close_output_file()
353
354
355def add_subdirs_to_set(dir_, dir_set):
356    subdirs = os.listdir(dir_)
357    for subdir in subdirs:
358        subdir = os.path.join(dir_, subdir)
359        if os.path.isdir(subdir):
360            dir_set.add(subdir)
361
362
363def include_directories(*paths, is_system=False):
364    if meson_translator.host_machine == 'android':
365        return impl.IncludeDirectories('', impl.get_include_dirs(paths))
366    # elif host_machine == 'fuchsia'
367    dirs = impl.get_include_dirs(paths)
368    name = dirs[0].replace('/', '_')
369    if is_system:
370        name += '_sys'
371
372    global _gIncludeDirectories
373    if name not in _gIncludeDirectories:
374        # Mesa likes to include files at a level down from the include path,
375        # so ensure Bazel allows this by including all of the possibilities.
376        # Can't repeat entries so use a set.
377        dir_set = set()
378        for dir_ in dirs:
379            dir_ = os.path.normpath(dir_)
380            dir_set.add(dir_)
381            add_subdirs_to_set(dir_, dir_set)
382
383        # HACK: For special cases we go down two levels:
384        # - Mesa vulkan runtime does #include "vulkan/wsi/..."
385        paths_needing_two_levels = ['src/vulkan']
386        for dir_ in list(dir_set):
387            if dir_ in paths_needing_two_levels:
388                add_subdirs_to_set(dir_, dir_set)
389
390        include_dir = IncludeDirectories()
391        include_dir.name = name
392
393        for dir_ in dir_set:
394            include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.h')))
395            include_dir.dirs.append(os.path.normpath(os.path.join(dir_, '*.c')))
396        include_dir.visibility.append('//visibility:public')
397        _gIncludeDirectories[name] = impl.IncludeDirectories(name, dirs)
398        meson_translator.meson_state.include_dirs.append(include_dir)
399    return _gIncludeDirectories[name]
400
401
402def module_import(name: str):
403    if name == 'python':
404        return impl.PythonModule()
405    if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'fuchsia':
406        return BazelPkgConfigModule()
407    if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'android':
408        return impl.PkgConfigModule()
409    if name == 'pkgconfig' and meson_translator.host_machine.lower() == 'linux':
410        return impl.PkgConfigModule()
411    exit(
412        f'Unhandled module: "{name}" for host machine: "{meson_translator.host_machine}"'
413    )
414
415
416def load_dependencies():
417    impl.load_dependencies(meson_translator.config_file)
418
419
420def dependency(
421    *names,
422    required=True,
423    version='',
424    allow_fallback=False,
425    method='',
426    modules=[],
427    optional_modules=[],
428    static=True,
429    fallback=[],
430    include_type='',
431    native=False,
432):
433    return impl.dependency(*names, required=required, version=version)
434
435
436def static_library(
437    target_name,
438    *source_files,
439    c_args=[],
440    cpp_args=[],
441    c_pch='',
442    build_by_default=False,
443    build_rpath='',
444    d_debug=[],
445    d_import_dirs=[],
446    d_module_versions=[],
447    d_unittest=False,
448    dependencies=[],
449    extra_files='',
450    gnu_symbol_visibility='',
451    gui_app=False,
452    implicit_include_directories=False,
453    include_directories=[],
454    install=False,
455    install_dir='',
456    install_mode=[],
457    install_rpath='',
458    install_tag='',
459    link_args=[],
460    link_depends='',
461    link_language='',
462    link_whole=[],
463    link_with=[],
464    name_prefix='',
465    name_suffix='',
466    native=False,
467    objects=[],
468    override_options=[],
469    pic=False,
470    prelink=False,
471    rust_abi='',
472    rust_crate_type='',
473    rust_dependency_map={},
474    sources='',
475    vala_args=[],
476    win_subsystem='',
477):
478    print('static_library: ' + target_name)
479
480    link_with = impl.get_linear_list(link_with)
481    link_whole = impl.get_linear_list(link_whole)
482
483    # Emitting link_with/link_whole into a static library isn't useful for building,
484    # shared libraries must specify the whole chain of dependencies.
485    # Leaving them here for traceability though maybe it's less confusing to remove them?
486    _emit_builtin_target(
487        target_name,
488        *source_files,
489        c_args=c_args,
490        cpp_args=cpp_args,
491        dependencies=dependencies,
492        include_directories=include_directories,
493        link_with=link_with,
494        link_whole=link_whole,
495        builtin_type_name='cc_library_static',
496        static=meson_translator.is_bazel(),
497        library_type=LibraryType.LibraryStatic,
498    )
499
500    return impl.StaticLibrary(target_name, link_with=link_with, link_whole=link_whole)
501
502
503def _get_sources(input_sources, sources, generated_sources, generated_headers):
504    def generate_sources_fuchsia(source_):
505        if type(source_) is impl.CustomTarget:
506            generated_sources.add(source_.target_name())
507            for out in source_.outputs:
508                sources.add(out)
509        elif type(source_) is impl.CustomTargetItem:
510            target = source_.target
511            generated_sources.add(target.target_name())
512            sources.add(target.outputs[source_.index])
513
514    def generate_sources_android(source_):
515        if type(source_) is impl.CustomTarget:
516            if source_.generates_h():
517                generated_headers.add(source_.target_name_h())
518            if source_.generates_c():
519                generated_sources.add(source_.target_name_c())
520        elif type(source_) is impl.CustomTargetItem:
521            source_ = source_.target
522            if source_.generates_h():
523                generated_headers.add(source_.target_name_h())
524            if source_.generates_c():
525                generated_sources.add(source_.target_name_c())
526
527    for source in input_sources:
528        if type(source) is list:
529            _get_sources(source, sources, generated_sources, generated_headers)
530        elif type(source) is impl.CustomTarget or type(source) is impl.CustomTargetItem:
531            if meson_translator.is_bazel():
532                generate_sources_fuchsia(source)
533            else:  # is_soong
534                generate_sources_android(source)
535        elif type(source) is impl.File:
536            sources.add(source.name)
537        elif type(source) is str:
538            sources.add(impl.get_relative_dir(source))
539        else:
540            exit(f'source type not handled: {type(source)}')
541
542
543# TODO(bpnguyen): Implement some bazel/soong parser that would merge
544# logic from this function to _emit_builtin_target_android
545def _emit_builtin_target_fuchsia(
546    target_name,
547    *source,
548    static=False,
549    c_args=[],
550    cpp_args=[],
551    dependencies=[],
552    include_directories=[],
553    link_with=[],
554    link_whole=[],
555    library_type=LibraryType.Library,
556):
557    sl = StaticLibrary()
558    target_name_so = target_name
559    target_name = target_name if static else '_' + target_name
560    sl.name = target_name
561    sl.library_type = library_type
562
563    srcs = set()
564    generated_sources = set()
565    generated_headers = set()
566    generated_header_files = []
567    for source_arg in source:
568        assert type(source_arg) is list
569        _get_sources(source_arg, srcs, generated_sources, generated_headers)
570
571    deps = impl.get_set_of_deps(dependencies)
572    include_directories = impl.get_include_directories(include_directories)
573    static_libs = []
574    whole_static_libs = []
575    shared_libs = []
576    for dep in deps:
577        print('  dep: ' + dep.name)
578        for src in impl.get_linear_list([dep.sources]):
579            if type(src) is impl.CustomTargetItem:
580                generated_headers.add(src.target.target_name_h())
581                generated_header_files.extend(src.target.header_outputs())
582            elif type(src) is impl.CustomTarget:
583                generated_headers.add(src.target_name_h())
584                generated_header_files.extend(src.header_outputs())
585            else:
586                exit('Unhandled source dependency: ' + str(type(src)))
587        include_directories.extend(
588            impl.get_include_directories(dep.include_directories)
589        )
590        for target in impl.get_static_libs([dep.link_with]):
591            assert type(target) is impl.StaticLibrary
592            static_libs.append(target.target_name)
593        for target in impl.get_linear_list([dep.link_whole]):
594            assert type(target) is impl.StaticLibrary
595            whole_static_libs.append(target.target_name)
596        for target in dep.targets:
597            if target.target_type == impl.DependencyTargetType.SHARED_LIBRARY:
598                shared_libs.append(target.target_name)
599            elif target.target_type == impl.DependencyTargetType.STATIC_LIBRARY:
600                static_libs.append(target.target_name)
601            elif target.target_type == impl.DependencyTargetType.HEADER_LIBRARY:
602                exit('Header library not supported')
603        c_args.append(dep.compile_args)
604        cpp_args.append(dep.compile_args)
605
606    for target in impl.get_static_libs(link_with):
607        if type(target) is impl.StaticLibrary:
608            static_libs.append(target.target_name)
609        else:
610            exit('Unhandled link_with type: ' + str(type(target)))
611
612    for target in impl.get_whole_static_libs(link_whole):
613        if type(target) is impl.StaticLibrary:
614            whole_static_libs.append(target.target_name())
615        else:
616            exit('Unhandled link_whole type: ' + str(type(target)))
617
618    # Android turns all warnings into errors but thirdparty projects typically can't handle that
619    cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args)
620    cppflags = ['-Wno-error'] + impl.get_linear_list(
621        impl.get_project_cppflags() + cpp_args
622    )
623
624    has_c_files = False
625    for src in srcs:
626        if src.endswith('.c'):
627            has_c_files = True
628        sl.srcs.append(src)
629
630    # For Bazel to find headers in the "current area", we have to include
631    # not just the headers in the relative dir, but also relative subdirs
632    # that aren't themselves areas (containing meson.build).
633    # We only look one level deep.
634    local_include_dirs = [impl.get_relative_dir()]
635    local_entries = (
636        [] if impl.get_relative_dir() == '' else os.listdir(impl.get_relative_dir())
637    )
638    for entry in local_entries:
639        entry = os.path.join(impl.get_relative_dir(), entry)
640        if os.path.isdir(entry):
641            subdir_entries = os.listdir(entry)
642            if 'meson.build' not in subdir_entries:
643                local_include_dirs.append(entry)
644
645    for hdr in set(generated_header_files):
646        sl.generated_headers.append(hdr)
647    for hdr in local_include_dirs:
648        sl.local_include_dirs.append(os.path.normpath(os.path.join(hdr, '*.h')))
649    # Needed for subdir sources
650    sl.copts.append(f'-I {impl.get_relative_dir()}')
651    sl.copts.append(f'-I $(GENDIR)/{impl.get_relative_dir()}')
652    for inc in include_directories:
653        for dir in inc.dirs:
654            sl.copts.append(f'-I {dir}')
655            sl.copts.append(f'-I $(GENDIR)/{dir}')
656
657    if has_c_files:
658        for arg in cflags:
659            # Double escape double quotations
660            arg = re.sub(r'"', '\\\\\\"', arg)
661            sl.copts.append(arg)
662    else:
663        for arg in cppflags:
664            # Double escape double quotations
665            arg = re.sub(r'"', '\\\\\\"', arg)
666            sl.copts.append(arg)
667
668    # Ensure bazel deps are unique
669    bazel_deps = set()
670    for lib in static_libs:
671        bazel_deps.add(lib)
672    for lib in whole_static_libs:
673        bazel_deps.add(lib)
674    for inc in include_directories:
675        bazel_deps.add(inc.name)
676    for target in generated_headers:
677        bazel_deps.add(target)
678    for target in generated_sources:
679        bazel_deps.add(target)
680
681    for bdep in bazel_deps:
682        sl.deps.append(bdep)
683
684    sl.target_compatible_with.append('@platforms//os:fuchsia')
685    sl.visibility.append('//visibility:public')
686
687    meson_translator.meson_state.static_libraries.append(sl)
688
689    if not static:
690        shared_sl = StaticLibrary()
691        shared_sl.library_type = LibraryType.LibraryShared
692        shared_sl.name = target_name_so
693        shared_sl.deps.append(target_name)
694        meson_translator.meson_state.static_libraries.append(shared_sl)
695
696
697# TODO(bpnguyen): Implement some bazel/soong parser that would merge
698# logic from this function to _emit_builtin_target_fuchsia
699def _emit_builtin_target_android(
700    target_name,
701    *source,
702    c_args=[],
703    cpp_args=[],
704    dependencies=[],
705    include_directories=[],
706    link_with=[],
707    link_whole=[],
708    builtin_type_name='',
709    static=False,
710    library_type=LibraryType.Library,
711):
712    static_lib = StaticLibrary()
713    static_lib.name = target_name
714    static_lib.library_type = library_type
715
716    srcs = set()
717    generated_sources = set()
718    generated_headers = set()
719    for source_arg in source:
720        assert type(source_arg) is list
721        _get_sources(source_arg, srcs, generated_sources, generated_headers)
722
723    deps = impl.get_set_of_deps(dependencies)
724    include_directories = [impl.get_relative_dir()] + impl.get_include_dirs(
725        include_directories
726    )
727    static_libs = []
728    whole_static_libs = []
729    shared_libs = []
730    header_libs = []
731    for dep in deps:
732        print('  dep: ' + dep.name)
733        for src in impl.get_linear_list([dep.sources]):
734            if type(src) is impl.CustomTargetItem:
735                generated_headers.add(src.target.target_name_h())
736            elif type(src) is impl.CustomTarget:
737                generated_headers.add(src.target_name_h())
738            else:
739                exit('Unhandled source dependency: ' + str(type(src)))
740        include_directories.extend(impl.get_include_dirs(dep.include_directories))
741        for target in impl.get_static_libs([dep.link_with]):
742            assert type(target) is impl.StaticLibrary
743            static_libs.append(target.target_name)
744        for target in impl.get_linear_list([dep.link_whole]):
745            assert type(target) is impl.StaticLibrary
746            whole_static_libs.append(target.target_name)
747        for target in dep.targets:
748            if target.target_type is impl.DependencyTargetType.SHARED_LIBRARY:
749                shared_libs.append(target.target_name)
750            elif target.target_type is impl.DependencyTargetType.STATIC_LIBRARY:
751                static_libs.append(target.target_name)
752            elif target.target_type is impl.DependencyTargetType.HEADER_LIBRARY:
753                header_libs.append(target.target_name)
754        c_args.append(dep.compile_args)
755        cpp_args.append(dep.compile_args)
756
757    for target in impl.get_static_libs(link_with):
758        if type(target) is impl.StaticLibrary:
759            static_libs.append(target.target_name)
760        else:
761            exit(f'Unhandled link_with type: {type(target)}')
762
763    for target in impl.get_whole_static_libs(link_whole):
764        if type(target) is impl.StaticLibrary:
765            whole_static_libs.append(target.target_name())
766        else:
767            exit(f'Unhandled link_whole type: {type(target)}')
768
769    # Android turns all warnings into errors but thirdparty projects typically can't handle that
770    cflags = ['-Wno-error'] + impl.get_linear_list(impl.get_project_cflags() + c_args)
771    cppflags = ['-Wno-error'] + impl.get_linear_list(
772        impl.get_project_cppflags() + cpp_args
773    )
774
775    for src in srcs:
776        # Filter out header files
777        if not src.endswith('.h'):
778            static_lib.srcs.append(src)
779    for generated in generated_headers:
780        static_lib.generated_headers.append(generated)
781    for generated in generated_sources:
782        static_lib.generated_sources.append(generated)
783
784    for arg in impl.get_project_options():
785        if arg.name == 'c_std':
786            static_lib.cstd = arg.value
787        elif arg.name == 'cpp_std':
788            static_lib.cpp_std = arg.value
789
790    for arg in cflags:
791        # Escape double quotations
792        arg = re.sub(r'"', '\\"', arg)
793        static_lib.conlyflags.append(arg)
794    for arg in cppflags:
795        # Escape double quotations
796        arg = re.sub(r'"', '\\"', arg)
797        static_lib.cppflags.append(arg)
798
799    for inc in include_directories:
800        static_lib.local_include_dirs.append(inc)
801
802    for lib in static_libs:
803        static_lib.static_libs.append(lib)
804
805    for lib in whole_static_libs:
806        static_lib.whole_static_libs.append(lib)
807
808    for lib in shared_libs:
809        static_lib.shared_libs.append(lib)
810
811    for lib in header_libs:
812        static_lib.header_libs.append(lib)
813
814    meson_translator.meson_state.static_libraries.append(static_lib)
815
816
817def _emit_builtin_target(
818    target_name,
819    *source,
820    c_args=[],
821    cpp_args=[],
822    dependencies=[],
823    include_directories=[],
824    link_with=[],
825    link_whole=[],
826    builtin_type_name='',
827    static=False,
828    library_type=LibraryType.Library,
829):
830    if meson_translator.is_bazel():
831        _emit_builtin_target_fuchsia(
832            target_name,
833            *source,
834            c_args=c_args,
835            cpp_args=cpp_args,
836            dependencies=dependencies,
837            include_directories=include_directories,
838            link_with=link_with,
839            link_whole=link_whole,
840            static=static,
841            library_type=library_type,
842        )
843    else:  # meson_translator.is_soong()
844        _emit_builtin_target_android(
845            target_name,
846            *source,
847            c_args=c_args,
848            cpp_args=cpp_args,
849            dependencies=dependencies,
850            include_directories=include_directories,
851            link_with=link_with,
852            link_whole=link_whole,
853            builtin_type_name=builtin_type_name,
854            library_type=library_type,
855        )
856
857
858def shared_library(
859    target_name,
860    *source,
861    c_args=[],
862    cpp_args=[],
863    c_pch='',
864    build_by_default=False,
865    build_rpath='',
866    d_debug=[],
867    d_import_dirs=[],
868    d_module_versions=[],
869    d_unittest=False,
870    darwin_versions='',
871    dependencies=[],
872    extra_files='',
873    gnu_symbol_visibility='',
874    gui_app=False,
875    implicit_include_directories=False,
876    include_directories=[],
877    install=False,
878    install_dir='',
879    install_mode=[],
880    install_rpath='',
881    install_tag='',
882    link_args=[],
883    link_depends=[],
884    link_language='',
885    link_whole=[],
886    link_with=[],
887    name_prefix='',
888    name_suffix='',
889    native=False,
890    objects=[],
891    override_options=[],
892    rust_abi='',
893    rust_crate_type='',
894    rust_dependency_map={},
895    sources=[],
896    soversion='',
897    vala_args=[],
898    version='',
899    vs_module_defs='',
900    win_subsystem='',
901):
902    print('shared_library: ' + target_name)
903
904    link_with = impl.get_linear_list([link_with])
905    link_whole = impl.get_linear_list([link_whole])
906
907    _emit_builtin_target(
908        target_name,
909        *source,
910        c_args=c_args,
911        static=False,
912        cpp_args=cpp_args,
913        dependencies=dependencies,
914        include_directories=include_directories,
915        link_with=link_with,
916        link_whole=link_whole,
917        builtin_type_name='cc_library_shared',
918        library_type=LibraryType.LibraryStatic,
919    )
920    return impl.SharedLibrary(target_name)
921
922
923def _process_target_name(name):
924    name = re.sub(r'[\[\]]', '', name)
925    return name
926
927
928def _location_wrapper(name_or_list) -> str | list[str]:
929    if isinstance(name_or_list, list):
930        ret = []
931        for i in name_or_list:
932            ret.append(f'$(location {i})')
933        return ret
934
935    assert isinstance(name_or_list, str)
936    return f'$(location {name_or_list})'
937
938
939def _is_header(name):
940    return re.search(r'\.h[xx|pp]?$', name) is not None
941
942
943def _is_source(name):
944    return re.search(r'\.c[c|xx|pp]?$', name) is not None
945
946
947def _get_command_args(
948    command,
949    input,
950    output,
951    deps,
952    location_wrap=False,
953    obfuscate_output_c=False,
954    obfuscate_output_h=False,
955    obfuscate_suffix='',
956):
957    args = []
958    gendir = 'GENDIR' if meson_translator.is_bazel() else 'genDir'
959    for command_item in command[1:]:
960        if isinstance(command_item, list):
961            for item in command_item:
962                if type(item) is impl.File:
963                    args.append(
964                        _location_wrapper(item.name) if location_wrap else item.name
965                    )
966                elif type(item) is str:
967                    args.append(item)
968            continue
969
970        assert type(command_item) is str
971        match = re.match(r'@INPUT([0-9])?@', command_item)
972        if match is not None:
973            if match.group(1) is not None:
974                input_index = int(match.group(1))
975                input_list = impl.get_list_of_relative_inputs(input[input_index])
976            else:
977                input_list = impl.get_list_of_relative_inputs(input)
978            args.extend(_location_wrapper(input_list) if location_wrap else input_list)
979            continue
980
981        match = re.match(r'(.*?)@OUTPUT([0-9])?@', command_item)
982        if match is not None:
983            output_list = []
984            if match.group(2) is not None:
985                output_index = int(match.group(2))
986                selected_output = (
987                    output[output_index] if isinstance(output, list) else output
988                )
989                output_list.append(impl.get_relative_gen_dir(selected_output))
990            elif isinstance(output, list):
991                for out in output:
992                    output_list.append(impl.get_relative_gen_dir(out))
993            else:
994                output_list.append(impl.get_relative_gen_dir(output))
995            for out in output_list:
996                if _is_header(out) and obfuscate_output_h:
997                    args.append(
998                        match.group(1) + f'$({gendir})/{out}' if location_wrap else out
999                    )
1000                else:
1001                    if _is_source(out) and obfuscate_output_c:
1002                        out += obfuscate_suffix
1003                    args.append(
1004                        match.group(1) + _location_wrapper(out)
1005                        if location_wrap
1006                        else out
1007                    )
1008            continue
1009
1010        # Assume used to locate generated outputs
1011        match = re.match(r'(.*?)@CURRENT_BUILD_DIR@', command_item)
1012        if match is not None:
1013            args.append(f'$({gendir})' + '/' + impl.get_relative_dir())
1014            continue
1015
1016        if meson_translator.is_bazel():
1017            match = re.match(r'@PROJECT_BUILD_ROOT@(.*)', command_item)
1018            if match is not None:
1019                args.append(f'$({gendir}){match.group(1)}')
1020                continue
1021
1022        # A plain arg
1023        if ' ' in command_item:
1024            args.append(f"'{command_item}'")
1025        else:
1026            args.append(command_item)
1027
1028    return args
1029
1030
1031def library(
1032    target_name,
1033    *sources,
1034    c_args=[],
1035    install=False,
1036    link_args=[],
1037    vs_module_defs='',
1038    version='',
1039):
1040    print('library: ' + target_name)
1041
1042    return static_library(
1043        target_name,
1044        *sources,
1045        c_args=c_args,
1046        install=install,
1047        link_args=link_args,
1048        sources=sources,
1049    )
1050
1051
1052# Assume dependencies of custom targets are custom targets that are generating
1053# python scripts; build a python path of their locations.
1054def _get_python_path(deps):
1055    python_path = ''
1056    for index, dep in enumerate(deps):
1057        assert type(dep) is impl.CustomTarget
1058        if index > 0:
1059            python_path += ':'
1060        python_path += '`dirname %s`' % _location_wrapper(':%s' % dep.target_name())
1061    return python_path
1062
1063
1064def _get_export_include_dirs():
1065    dirs = [impl.get_relative_dir()]
1066    # HACK for source files that expect that include generated files like:
1067    # include "vulkan/runtime/...h"
1068    if impl.get_relative_dir().startswith('src'):
1069        dirs.append('src')
1070    return dirs
1071
1072
1073def _process_wrapped_args_for_python(
1074    wrapped_args, python_script, python_script_target_name, deps
1075):
1076    # The python script arg should be replaced with the python binary target name
1077    args = impl.replace_wrapped_input_with_target(
1078        wrapped_args, python_script, python_script_target_name
1079    )
1080    if meson_translator.is_bazel():
1081        return args
1082    # else is_soong():
1083    python_path = 'PYTHONPATH='
1084    # Python scripts expect to be able to import other scripts from the same directory, but this
1085    # doesn't work in the soong execution environment, so we have to explicitly add the script
1086    # dir. We can't use $(location python_binary) because that is missing the relative path;
1087    # instead we can use $(location python_script), which happens to work, and we're careful to
1088    # ensure the script is in the list of sources even when it's used as the command directly.
1089    python_path += '`dirname $(location %s)`' % python_script
1090    # Also ensure that scripts generated by dependent custom targets can be imported.
1091    if type(deps) is impl.CustomTarget:
1092        python_path += ':' + _get_python_path([deps])
1093    if type(deps) is list:
1094        python_path += ':' + _get_python_path(deps)
1095    args.insert(0, python_path)
1096    return args
1097
1098
1099# TODO(bpnguyen): merge custom_target
1100def custom_target(
1101    target_name: str,
1102    build_always=False,
1103    build_always_stale=False,
1104    build_by_default=False,
1105    capture=False,
1106    command=[],
1107    console=False,
1108    depend_files=[],
1109    depends=[],
1110    depfile='',
1111    env=[],
1112    feed=False,
1113    input=[],
1114    install=False,
1115    install_dir='',
1116    install_mode=[],
1117    install_tag=[],
1118    output=[],
1119):
1120    target_name = _process_target_name(target_name)
1121    print('Custom target: ' + target_name)
1122    assert type(command) is list
1123    program = command[0]
1124    program_args = []
1125
1126    # The program can be an array that includes arguments
1127    if isinstance(program, list):
1128        for arg in program[1:]:
1129            assert type(arg) is str
1130            program_args.append(arg)
1131        program = program[0]
1132
1133    assert isinstance(program, impl.Program)
1134    assert program.found()
1135
1136    args = program_args + _get_command_args(command, input, output, depends)
1137
1138    # Python scripts need special handling to find mako library
1139    python_script = ''
1140    python_script_target_name = ''
1141    if program.command.endswith('.py'):
1142        python_script = program.command
1143    else:
1144        for index, arg in enumerate(args):
1145            if arg.endswith('.py'):
1146                python_script = arg
1147                break
1148    if python_script != '':
1149        python_script_target_name = target_name + '_' + os.path.basename(python_script)
1150        srcs = [python_script] + impl.get_list_of_relative_inputs(depend_files)
1151        python_custom_target = PythonCustomTarget()
1152        python_custom_target.name = python_script_target_name
1153        python_custom_target.main = python_script
1154        for src in set(srcs):
1155            if src.endswith('.py'):
1156                python_custom_target.srcs.append(src)
1157        for src in set(srcs):
1158            if src.endswith('.py'):
1159                python_custom_target.imports.append(os.path.dirname(src))
1160
1161        meson_translator.meson_state.custom_py_targets.append(python_custom_target)
1162
1163    relative_inputs = impl.get_list_of_relative_inputs(input)
1164    # We use python_host_binary instead of calling python scripts directly;
1165    # however there's an issue with python locating modules in the same directory
1166    # as the script; to workaround that (see _process_wrapped_args_for_python) we
1167    # ensure the script is listed in the genrule targets.
1168    if python_script != '' and python_script not in relative_inputs:
1169        relative_inputs.append(python_script)
1170    relative_inputs.extend(impl.get_list_of_relative_inputs(depend_files))
1171
1172    relative_inputs_set = set()
1173    if meson_translator.is_soong():
1174        for src in relative_inputs:
1175            relative_inputs_set.add(src)
1176
1177    relative_outputs = []
1178    if isinstance(output, list):
1179        for file in output:
1180            relative_outputs.append(impl.get_relative_gen_dir(file))
1181    else:
1182        assert type(output) is str
1183        relative_outputs.append(impl.get_relative_gen_dir(output))
1184
1185    generates_h = False
1186    generates_c = False
1187    custom_target_ = None
1188    if meson_translator.is_soong():
1189        # Soong requires genrule to generate only headers OR non-headers
1190        for out in relative_outputs:
1191            if _is_header(out):
1192                generates_h = True
1193            if _is_source(out):
1194                generates_c = True
1195        custom_target_ = impl.CustomTarget(
1196            target_name, relative_outputs, generates_h, generates_c
1197        )
1198    else:  # is_bazel:
1199        custom_target_ = impl.CustomTarget(target_name, relative_outputs)
1200
1201    program_command = program.command
1202    if meson_translator.is_soong():
1203        if program_command == 'bison':
1204            program_command_arg = 'M4=$(location m4) $(location bison)'
1205        elif program_command == 'flex':
1206            program_command_arg = 'M4=$(location m4) $(location flex)'
1207        elif program_command.endswith('.py'):
1208            program_command_arg = _location_wrapper(program_command)
1209        else:
1210            program_command_arg = program_command
1211
1212        program_args = [program_command_arg] + program_args
1213
1214        if custom_target_.generates_h() and custom_target_.generates_c():
1215            # Make a rule for only the headers
1216            obfuscate_suffix = '.dummy.h'
1217            wrapped_args = program_args + _get_command_args(
1218                command,
1219                input,
1220                output,
1221                depends,
1222                location_wrap=True,
1223                obfuscate_output_c=True,
1224                obfuscate_suffix=obfuscate_suffix,
1225            )
1226            if python_script:
1227                wrapped_args = _process_wrapped_args_for_python(
1228                    wrapped_args, python_script, python_script_target_name, depends
1229                )
1230
1231            command_line = impl.get_command_line_from_args(wrapped_args)
1232            if capture:
1233                command_line += ' > %s' % _location_wrapper(
1234                    impl.get_relative_gen_dir(output)
1235                )
1236            ct = CustomTarget()
1237            ct.name = custom_target_.target_name_h()
1238            for src in relative_inputs_set:
1239                ct.srcs.append(src)
1240            for dep in depends:
1241                assert type(dep) is impl.CustomTarget
1242                ct.srcs.append(':' + dep.target_name())
1243            for out in relative_outputs:
1244                if _is_source(out):
1245                    out += obfuscate_suffix
1246                    # The scripts may still write to the assumed .c file, ensure the obfuscated
1247                    # file exists
1248                    command_line += (
1249                        "; echo '//nothing to see here' > " + _location_wrapper(out)
1250                    )
1251                ct.out.append(out)
1252            if python_script_target_name != '':
1253                ct.tools.append(python_script_target_name)
1254
1255            if program_command == 'bison' or program_command == 'flex':
1256                ct.tools.append('m4')
1257                ct.tools.append(program_command)
1258
1259            for dir in _get_export_include_dirs():
1260                ct.export_include_dirs.append(dir)
1261
1262            ct.cmd = command_line
1263            meson_translator.meson_state.custom_targets.append(ct)
1264            # Make a rule for only the sources
1265            obfuscate_suffix = '.dummy.c'
1266            wrapped_args = program_args + _get_command_args(
1267                command,
1268                input,
1269                output,
1270                depends,
1271                location_wrap=True,
1272                obfuscate_output_h=True,
1273                obfuscate_suffix=obfuscate_suffix,
1274            )
1275            if python_script:
1276                wrapped_args = _process_wrapped_args_for_python(
1277                    wrapped_args, python_script, python_script_target_name, depends
1278                )
1279
1280            command_line = impl.get_command_line_from_args(wrapped_args)
1281            if capture:
1282                command_line += ' > %s' % _location_wrapper(
1283                    impl.get_relative_gen_dir(output)
1284                )
1285
1286            # We keep the header as an output with an obfuscated name because some scripts insist
1287            # on having --out-h (like vk_entrypoints_gen.py). When Soong depends on this genrule
1288            # it'll insist on compiling all the outputs, so we replace the content of all header
1289            # outputs.
1290            ct_ = CustomTarget()
1291            ct_.name = custom_target_.target_name_c()
1292
1293            for src in relative_inputs_set:
1294                ct_.srcs.append(src)
1295            for dep in depends:
1296                assert type(dep) is impl.CustomTarget
1297                ct_.srcs.append(':' + dep.target_name())
1298            for out in relative_outputs:
1299                if _is_header(out):
1300                    out += obfuscate_suffix
1301                    ct_.out.append(out)
1302                    # Remove the content because Soong will compile this dummy source file
1303                    command_line += (
1304                        "; echo '//nothing to see here' > " + _location_wrapper(out)
1305                    )
1306                ct_.out.append(out)
1307            if python_script_target_name != '':
1308                ct_.tools.append(python_script_target_name)
1309            if program_command == 'bison' or program_command == 'flex':
1310                ct.tools.append('m4')
1311                ct.tools.append(program_command)
1312            ct_.cmd = command_line
1313            meson_translator.meson_state.custom_targets.append(ct_)
1314            return custom_target_
1315        else:
1316            wrapped_args = program_args + _get_command_args(
1317                command, input, output, depends, location_wrap=True
1318            )
1319            if python_script:
1320                wrapped_args = _process_wrapped_args_for_python(
1321                    wrapped_args, python_script, python_script_target_name, depends
1322                )
1323
1324            command_line = impl.get_command_line_from_args(wrapped_args)
1325            if capture:
1326                command_line += ' > %s' % _location_wrapper(
1327                    impl.get_relative_gen_dir(output)
1328                )
1329            ct = CustomTarget()
1330            ct.name = custom_target_.target_name()
1331            for src in relative_inputs_set:
1332                ct.srcs.append(src)
1333            for dep in depends:
1334                assert type(dep) is impl.CustomTarget
1335                ct.srcs.append(':' + dep.target_name())
1336            for out in relative_outputs:
1337                ct.out.append(out)
1338            if python_script_target_name != '':
1339                ct.tools.append(python_script_target_name)
1340            if program_command == 'bison' or program_command == 'flex':
1341                ct.tools.append('m4')
1342                ct.tools.append(program_command)
1343            for dir_ in _get_export_include_dirs():
1344                ct.export_include_dirs.append(dir_)
1345            ct.cmd = command_line
1346            meson_translator.meson_state.custom_targets.append(ct)
1347    else:  # is_bazel
1348        if program_command.endswith('.py'):
1349            program_command_arg = _location_wrapper(program_command)
1350        else:
1351            program_command_arg = program_command
1352
1353        program_args = [program_command_arg] + program_args
1354
1355        wrapped_args = program_args + _get_command_args(
1356            command, input, output, depends, location_wrap=True
1357        )
1358        if python_script:
1359            wrapped_args = _process_wrapped_args_for_python(
1360                wrapped_args, python_script, python_script_target_name, depends
1361            )
1362
1363        command_line = impl.get_command_line_from_args(wrapped_args)
1364        if capture:
1365            command_line += ' > %s' % _location_wrapper(
1366                impl.get_relative_gen_dir(output)
1367            )
1368
1369        ct = CustomTarget()
1370        ct.name = custom_target_.target_name()
1371        for src in set(relative_inputs):
1372            ct.srcs.append(src)
1373        for dep in depends:
1374            assert type(dep) is impl.CustomTarget
1375            ct.srcs.append(dep.target_name())
1376        for out in set(relative_outputs):
1377            ct.out.append(out)
1378        if python_script_target_name != '':
1379            ct.tools.append(python_script_target_name)
1380        ct.cmd = command_line
1381        meson_translator.meson_state.custom_targets.append(ct)
1382
1383    return custom_target_
1384