• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import re
3import meson_impl as impl
4
5
6class Compiler(impl.Compiler):
7
8  def is_symbol_supported(self, header: str, symbol: str):
9    if header == 'sys/sysmacros.h':
10      return False
11    return super().is_symbol_supported(header, symbol)
12
13  def is_function_supported(self, function):
14    if function == 'getrandom' or function == 'memfd_create':
15      return False
16    return super().is_function_supported(function)
17
18  def is_link_supported(self, name):
19    if name == 'strtod has locale support':
20      return False
21    return super().is_link_supported(name)
22
23  def has_header_symbol(
24      self,
25      header: str,
26      symbol: str,
27      args=[],
28      dependencies=[],
29      include_directories=[],
30      no_builtin_args=False,
31      prefix=[],
32      required=False,
33  ):
34    # Exclude what is currently not working.
35    result = self.is_symbol_supported(header, symbol)
36    print("has_header_symbol '%s', '%s': %s" % (header, symbol, str(result)))
37    return result
38
39  def check_header(self, header, prefix=''):
40    result = self.is_header_supported(header)
41    print("check_header '%s': %s" % (header, str(result)))
42    return result
43
44  def has_function(self, function, args=[], prefix='', dependencies=''):
45    # Exclude what is currently not working.
46    result = self.is_function_supported(function)
47    print("has_function '%s': %s" % (function, str(result)))
48    return result
49
50  def links(self, snippet, name, args=[], dependencies=[]):
51    # Exclude what is currently not working.
52    result = self.is_link_supported(name)
53    print("links '%s': %s" % (name, str(result)))
54    return result
55
56
57class PkgconfigModule:
58
59  def generate(self, lib, name='', description='', extra_cflags=[]):
60    impl.fprint('# package library')
61    impl.fprint('cc_library(')
62    impl.fprint('  name = "%s",' % name)
63    assert type(lib) == impl.StaticLibrary
64    impl.fprint('  deps = [ "%s" ],' % lib.target_name)
65    # This line tells Bazel to use -isystem for targets that depend on this one,
66    # which is needed for clients that include package headers with angle brackets.
67    impl.fprint('  includes = [ "." ],')
68    impl.fprint('  visibility = [ "//visibility:public" ],')
69    impl.fprint(')')
70
71
72# These globals exposed by the meson API
73meson = impl.Meson(Compiler())
74host_machine = impl.Machine('fuchsia')
75build_machine = impl.Machine('linux')
76
77_gIncludeDirectories = dict()
78
79
80def open_output_file():
81  impl.open_output_file(r'BUILD.bazel')
82
83
84def close_output_file():
85  impl.close_output_file()
86
87
88def load_config_file():
89  impl.load_config_file('python-build/generate_fuchsia_build.config')
90
91
92def include_directories(*paths, is_system=False):
93  dirs = impl.get_include_dirs(paths)
94  name = dirs[0].replace('/', '_')
95  if is_system:
96    name += '_sys'
97
98  global _gIncludeDirectories
99  if name not in _gIncludeDirectories:
100    # Mesa likes to include files at a level down from the include path,
101    # so ensure Bazel allows this by including all of the possibilities.
102    # Hopefully we don't need to go two or more levels down.
103    # Can't repeat entries so use a set.
104    dir_set = set()
105    for dir in dirs:
106      dir = os.path.normpath(dir)
107      dir_set.add(dir)
108      subdirs = os.listdir(dir)
109      for subdir in subdirs:
110        subdir = os.path.join(dir, subdir)
111        if os.path.isdir(subdir):
112          dir_set.add(subdir)
113
114    impl.fprint('# header library')
115    impl.fprint('cc_library(')
116    impl.fprint('  name = "%s",' % name)
117    impl.fprint('  hdrs = []')
118    for dir in dir_set:
119      impl.fprint(
120          '    + glob(["%s"])' % os.path.normpath(os.path.join(dir, '*.h'))
121      )
122      # C files included because...
123      impl.fprint(
124          '    + glob(["%s"])' % os.path.normpath(os.path.join(dir, '*.c'))
125      )
126    impl.fprint('  ,')
127    impl.fprint('  visibility = [ "//visibility:public" ],')
128    impl.fprint(')')
129    _gIncludeDirectories[name] = impl.IncludeDirectories(name, dirs)
130
131  return _gIncludeDirectories[name]
132
133
134def module_import(name: str):
135  if name == 'python':
136    return impl.PythonModule()
137  if name == 'pkgconfig':
138    return PkgconfigModule()
139  exit('Unhandled module: ' + name)
140
141
142def dependency(
143    *names,
144    required=True,
145    version='',
146    allow_fallback=False,
147    method='',
148    modules=[],
149    optional_modules=[],
150    static=True,
151    fallback=[],
152    include_type='',
153    native=False
154):
155  for name in names:
156    if name == 'zlib':
157      return impl.Dependency(
158          name,
159          version,
160          targets=[
161              impl.DependencyTarget(
162                  '@zlib//:zlib',
163                  impl.DependencyTargetType.STATIC_LIBRARY,
164              ),
165          ],
166          found=True,
167      )
168
169    if name == 'libmagma':
170      return impl.Dependency(
171          name,
172          targets=[
173              impl.DependencyTarget(
174                  '@fuchsia_sdk//pkg/magma_client',
175                  impl.DependencyTargetType.STATIC_LIBRARY,
176              )
177          ],
178          found=True,
179      )
180
181    if name == 'libmagma_virt':
182      return impl.Dependency(name, version, found=True)
183
184  # common deps
185  return impl.dependency(*names)
186
187
188def static_library(
189    target_name,
190    *source_files,
191    c_args=[],
192    cpp_args=[],
193    c_pch='',
194    build_by_default=False,
195    build_rpath='',
196    d_debug=[],
197    d_import_dirs=[],
198    d_module_versions=[],
199    d_unittest=False,
200    dependencies=[],
201    extra_files='',
202    gnu_symbol_visibility='',
203    gui_app=False,
204    implicit_include_directories=False,
205    include_directories=[],
206    install=False,
207    install_dir='',
208    install_mode=[],
209    install_rpath='',
210    install_tag='',
211    link_args=[],
212    link_depends='',
213    link_language='',
214    link_whole=[],
215    link_with=[],
216    name_prefix='',
217    name_suffix='',
218    native=False,
219    objects=[],
220    override_options=[],
221    pic=False,
222    prelink=False,
223    rust_abi='',
224    rust_crate_type='',
225    rust_dependency_map={},
226    sources='',
227    vala_args=[],
228    win_subsystem=''
229):
230  print('static_library: ' + target_name)
231
232  link_with = impl.get_linear_list(link_with)
233  link_whole = impl.get_linear_list(link_whole)
234
235  # Emitting link_with/link_whole into a static library isn't useful for building,
236  # shared libraries must specify the whole chain of dependencies.
237  # Leaving them here for traceability though maybe it's less confusing to remove them?
238  _emit_builtin_target(
239      target_name,
240      *source_files,
241      static=True,
242      c_args=c_args,
243      cpp_args=cpp_args,
244      dependencies=dependencies,
245      include_directories=include_directories,
246      link_with=link_with,
247      link_whole=link_whole,
248  )
249
250  return impl.StaticLibrary(
251      target_name, link_with=link_with, link_whole=link_whole
252  )
253
254
255def shared_library(
256    target_name,
257    *source,
258    c_args=[],
259    cpp_args=[],
260    c_pch='',
261    build_by_default=False,
262    build_rpath='',
263    d_debug=[],
264    d_import_dirs=[],
265    d_module_versions=[],
266    d_unittest=False,
267    darwin_versions='',
268    dependencies=[],
269    extra_files='',
270    gnu_symbol_visibility='',
271    gui_app=False,
272    implicit_include_directories=False,
273    include_directories=[],
274    install=False,
275    install_dir='',
276    install_mode=[],
277    install_rpath='',
278    install_tag='',
279    link_args=[],
280    link_depends=[],
281    link_language='',
282    link_whole=[],
283    link_with=[],
284    name_prefix='',
285    name_suffix='',
286    native=False,
287    objects=[],
288    override_options=[],
289    rust_abi='',
290    rust_crate_type='',
291    rust_dependency_map={},
292    sources=[],
293    soversion='',
294    vala_args=[],
295    version='',
296    vs_module_defs='',
297    win_subsystem=''
298):
299  print('shared_library: ' + target_name)
300
301  link_with = impl.get_linear_list([link_with])
302  link_whole = impl.get_linear_list([link_whole])
303
304  _emit_builtin_target(
305      target_name,
306      *source,
307      static=False,
308      c_args=c_args,
309      cpp_args=cpp_args,
310      dependencies=dependencies,
311      include_directories=include_directories,
312      link_with=link_with,
313      link_whole=link_whole,
314  )
315  return impl.SharedLibrary(target_name)
316
317
318def library(
319    target_name,
320    *sources,
321    c_args=[],
322    install=False,
323    link_args=[],
324    vs_module_defs='',
325    version=''
326):
327  print('library: ' + target_name)
328
329  return static_library(
330      target_name,
331      *sources,
332      c_args=c_args,
333      install=install,
334      link_args=link_args,
335      sources=sources,
336  )
337
338
339def _get_sources(input_sources, sources, generated_sources, generated_headers):
340  for source in input_sources:
341    if type(source) is list:
342      _get_sources(source, sources, generated_sources, generated_headers)
343    elif type(source) is impl.CustomTarget:
344      generated_sources.add(source.target_name())
345      for out in source._outputs:
346        sources.add(out)
347    elif type(source) is impl.CustomTargetItem:
348      target = source.target
349      generated_sources.add(target.target_name())
350      sources.add(target._outputs[source.index])
351    elif type(source) is impl.File:
352      sources.add(source.name)
353    elif type(source) is str:
354      sources.add(impl.get_relative_dir(source))
355    else:
356      exit('source type not handled: ' + str(type(source)))
357
358
359def _emit_builtin_target(
360    target_name,
361    *source,
362    static=False,
363    c_args=[],
364    cpp_args=[],
365    dependencies=[],
366    include_directories=[],
367    link_with=[],
368    link_whole=[]
369):
370  impl.fprint('cc_library(')
371  target_name_so = target_name
372  target_name = target_name if static else '_' + target_name
373  impl.fprint('  name = "%s",' % target_name)
374
375  srcs = set()
376  generated_sources = set()
377  generated_headers = set()
378  for source_arg in source:
379    assert type(source_arg) is list
380    _get_sources(source_arg, srcs, generated_sources, generated_headers)
381
382  deps = impl.get_set_of_deps(dependencies)
383  include_directories = impl.get_include_directories(include_directories)
384  static_libs = []
385  whole_static_libs = []
386  shared_libs = []
387  for dep in deps:
388    print('  dep: ' + dep.name)
389    for src in impl.get_linear_list([dep.sources]):
390      if type(src) == impl.CustomTargetItem:
391        generated_headers.add(src.target.target_name_h())
392      elif type(src) == impl.CustomTarget:
393        generated_headers.add(src.target_name_h())
394      else:
395        exit('Unhandled source dependency: ' + str(type(src)))
396    include_directories.extend(
397        impl.get_include_directories(dep.include_directories)
398    )
399    for target in impl.get_static_libs([dep.link_with]):
400      assert type(target) == impl.StaticLibrary
401      static_libs.append(target.target_name)
402    for target in impl.get_linear_list([dep.link_whole]):
403      assert type(target) == impl.StaticLibrary
404      whole_static_libs.append(target.target_name)
405    for target in dep.targets:
406      if target.target_type == impl.DependencyTargetType.SHARED_LIBRARY:
407        shared_libs.append(target.target_name)
408      elif target.target_type == impl.DependencyTargetType.STATIC_LIBRARY:
409        static_libs.append(target.target_name)
410      elif target.target_type == impl.DependencyTargetType.HEADER_LIBRARY:
411        exit('Header library not supported')
412
413  for target in impl.get_static_libs(link_with):
414    if type(target) == impl.StaticLibrary:
415      static_libs.append(target.target_name)
416    else:
417      exit('Unhandled link_with type: ' + str(type(target)))
418
419  for target in impl.get_whole_static_libs(link_whole):
420    if type(target) == impl.StaticLibrary:
421      whole_static_libs.append(target.target_name())
422    else:
423      exit('Unhandled link_whole type: ' + str(type(target)))
424
425  # Android turns all warnings into errors but thirdparty projects typically can't handle that
426  cflags = ['-Wno-error'] + impl.get_linear_list(
427      impl.get_project_cflags() + c_args
428  )
429  cppflags = ['-Wno-error'] + impl.get_linear_list(
430      impl.get_project_cppflags() + cpp_args
431  )
432
433  has_c_files = False
434  impl.fprint('  srcs = [')
435  for src in srcs:
436    if src.endswith('.c'):
437      has_c_files = True
438    impl.fprint('    "%s",' % src)
439  impl.fprint('  ],')
440
441  # For Bazel to find headers in the "current area", we have to include
442  # not just the headers in the relative dir, but also relative subdirs
443  # that aren't themselves areas (containing meson.build).
444  # We only look one level deep.
445  local_include_dirs = [impl.get_relative_dir()]
446  local_entries = (
447      []
448      if impl.get_relative_dir() == ''
449      else os.listdir(impl.get_relative_dir())
450  )
451  for entry in local_entries:
452    entry = os.path.join(impl.get_relative_dir(), entry)
453    if os.path.isdir(entry):
454      subdir_entries = os.listdir(entry)
455      if not 'meson.build' in subdir_entries:
456        local_include_dirs.append(entry)
457
458  impl.fprint(
459      '  # hdrs are our files that might be included; listed here so Bazel will'
460      ' allow them to be included'
461  )
462  impl.fprint('  hdrs = []')
463  for hdr in local_include_dirs:
464    impl.fprint(
465        '    + glob(["%s"])' % os.path.normpath(os.path.join(hdr, '*.h'))
466    )
467
468  impl.fprint('  ,')
469
470  impl.fprint('  copts = [')
471  # Needed for subdir sources
472  impl.fprint('    "-I %s",' % impl.get_relative_dir())
473  impl.fprint('    "-I $(GENDIR)/%s",' % impl.get_relative_dir())
474  for inc in include_directories:
475    for dir in inc.dirs:
476      impl.fprint('    "-I %s",' % dir)
477      impl.fprint('    "-I $(GENDIR)/%s",' % dir)
478
479  if has_c_files:
480    for arg in cflags:
481      # Double escape double quotations
482      arg = re.sub(r'"', '\\\\\\"', arg)
483      impl.fprint("    '%s'," % arg)
484  else:
485    for arg in cppflags:
486      # Double escape double quotations
487      arg = re.sub(r'"', '\\\\\\"', arg)
488      impl.fprint("    '%s'," % arg)
489
490  impl.fprint('  ],')
491
492  # Ensure bazel deps are unique
493  bazel_deps = set()
494  for lib in static_libs:
495    bazel_deps.add(lib)
496  for lib in whole_static_libs:
497    bazel_deps.add(lib)
498  for inc in include_directories:
499    bazel_deps.add(inc.name)
500  for target in generated_headers:
501    bazel_deps.add(target)
502  for target in generated_sources:
503    bazel_deps.add(target)
504
505  impl.fprint('  deps = [')
506  for bdep in bazel_deps:
507    impl.fprint('    "%s",' % bdep)
508  impl.fprint('  ],')
509
510  impl.fprint('  target_compatible_with = [ "@platforms//os:fuchsia" ],')
511  impl.fprint('  visibility = [ "//visibility:public" ],')
512  impl.fprint(')')
513
514  if not static:
515    impl.fprint('cc_shared_library(')
516    impl.fprint('  name = "%s",' % target_name_so)
517    impl.fprint('  deps = [')
518    impl.fprint('    "%s",' % target_name)
519    impl.fprint('  ],')
520    impl.fprint(')')
521
522
523def _process_target_name(name):
524  name = re.sub(r'[\[\]]', '', name)
525  return name
526
527
528# Returns string or list of string
529def _location_wrapper(name_or_list):
530  if isinstance(name_or_list, list):
531    ret = []
532    for i in name_or_list:
533      ret.append('$(location %s)' % i)
534    return ret
535
536  assert isinstance(name_or_list, str)
537  return '$(location %s)' % name_or_list
538
539
540def _is_header(name):
541  return re.search(r'\.h[xx|pp]?$', name) != None
542
543
544def _is_source(name):
545  return re.search(r'\.c[c|xx|pp]?$', name) != None
546
547
548def _get_command_args(
549    command,
550    input,
551    output,
552    deps,
553    location_wrap=False,
554    obfuscate_output_c=False,
555    obfuscate_output_h=False,
556    obfuscate_suffix='',
557):
558  args = []
559  for command_item in command[1:]:
560    if isinstance(command_item, list):
561      for item in command_item:
562        assert type(item) == impl.File
563        args.append(
564            _location_wrapper(item.name) if location_wrap else item.name
565        )
566      continue
567
568    assert type(command_item) is str
569    match = re.match(r'@INPUT([0-9])?@', command_item)
570    if match != None:
571      if match.group(1) != None:
572        input_index = int(match.group(1))
573        input_list = impl.get_list_of_relative_inputs(input[input_index])
574      else:
575        input_list = impl.get_list_of_relative_inputs(input)
576      args.extend(
577          _location_wrapper(input_list) if location_wrap else input_list
578      )
579      continue
580
581    match = re.match(r'(.*?)@OUTPUT([0-9])?@', command_item)
582    if match != None:
583      output_list = []
584      if match.group(2) != None:
585        output_index = int(match.group(2))
586        selected_output = (
587            output[output_index] if isinstance(output, list) else output
588        )
589        output_list.append(impl.get_relative_gen_dir(selected_output))
590      elif isinstance(output, list):
591        for out in output:
592          output_list.append(impl.get_relative_gen_dir(out))
593      else:
594        output_list.append(impl.get_relative_gen_dir(output))
595      for out in output_list:
596        if _is_header(out) and obfuscate_output_h:
597          args.append(
598              match.group(1) + '$(GENDIR)/%s' % out if location_wrap else out
599          )
600        else:
601          if _is_source(out) and obfuscate_output_c:
602            out += obfuscate_suffix
603          args.append(
604              match.group(1) + _location_wrapper(out) if location_wrap else out
605          )
606      continue
607
608    # Assume used to locate generated outputs
609    match = re.match(r'(.*?)@CURRENT_BUILD_DIR@', command_item)
610    if match != None:
611      args.append('$(GENDIR)' + '/' + impl.get_relative_dir())
612      continue
613
614    match = re.match(r'@PROJECT_BUILD_ROOT@(.*)', command_item)
615    if match != None:
616      args.append('$(GENDIR)%s' % match.group(1))
617      continue
618
619    # A plain arg
620    if ' ' in command_item:
621      args.append("'%s'" % command_item)
622    else:
623      args.append(command_item)
624
625  return args
626
627
628def _process_wrapped_args_for_python(
629    wrapped_args, python_script, python_script_target_name, deps
630):
631  # The python script arg should be replaced with the python binary target name
632  args = impl.replace_wrapped_input_with_target(
633      wrapped_args, python_script, python_script_target_name
634  )
635  return args
636
637
638def custom_target(
639    target_name: str,
640    build_always=False,
641    build_always_stale=False,
642    build_by_default=False,
643    capture=False,
644    command=[],
645    console=False,
646    depend_files=[],
647    depends=[],
648    depfile='',
649    env=[],
650    feed=False,
651    input=[],
652    install=False,
653    install_dir='',
654    install_mode=[],
655    install_tag=[],
656    output=[],
657):
658  target_name = _process_target_name(target_name)
659  print('Custom target: ' + target_name)
660  assert type(command) is list
661  program = command[0]
662  program_args = []
663
664  # The program can be an array that includes arguments
665  if isinstance(program, list):
666    for arg in program[1:]:
667      assert type(arg) is str
668      program_args.append(arg)
669    program = program[0]
670
671  assert isinstance(program, impl.Program)
672  assert program.found()
673
674  args = program_args + _get_command_args(command, input, output, depends)
675
676  # Python scripts need special handling to find mako library
677  python_script = ''
678  python_script_target_name = ''
679  if program.command.endswith('.py'):
680    python_script = program.command
681  else:
682    for index, arg in enumerate(args):
683      if arg.endswith('.py'):
684        python_script = arg
685        break
686
687  if python_script != '':
688    python_script_target_name = (
689        target_name + '_' + os.path.basename(python_script)
690    )
691    srcs = [python_script] + impl.get_list_of_relative_inputs(depend_files)
692    impl.fprint('py_binary(')
693    impl.fprint('  name = "%s",' % python_script_target_name)
694    impl.fprint('  main = "%s",' % python_script)
695    impl.fprint('  srcs = [')
696    for src in srcs:
697      if src.endswith('.py'):
698        impl.fprint('    "%s",' % src)
699    impl.fprint('  ],')
700    # So scripts can find other scripts
701    impl.fprint('  imports = [')
702    for src in srcs:
703      if src.endswith('.py'):
704        impl.fprint('    "%s",' % os.path.dirname(src))
705    impl.fprint('  ],')
706    impl.fprint(')')
707
708  relative_inputs = impl.get_list_of_relative_inputs(input)
709  # We use python_host_binary instead of calling python scripts directly;
710  # however there's an issue with python locating modules in the same directory
711  # as the script; to workaround that (see process_wrapped_args_for_python) we
712  # ensure the script is listed in the genrule targets.
713  if python_script != '' and not python_script in relative_inputs:
714    relative_inputs.append(python_script)
715  relative_inputs.extend(impl.get_list_of_relative_inputs(depend_files))
716
717  relative_outputs = []
718  if isinstance(output, list):
719    for file in output:
720      relative_outputs.append(impl.get_relative_gen_dir(file))
721  else:
722    assert type(output) == str
723    relative_outputs.append(impl.get_relative_gen_dir(output))
724
725  custom_target = impl.CustomTarget(target_name, relative_outputs)
726
727  program_command = program.command
728  if program_command.endswith('.py'):
729    program_command_arg = _location_wrapper(program_command)
730  else:
731    program_command_arg = program_command
732
733  program_args = [program_command_arg] + program_args
734
735  wrapped_args = program_args + _get_command_args(
736      command, input, output, depends, location_wrap=True
737  )
738  if python_script:
739    wrapped_args = _process_wrapped_args_for_python(
740        wrapped_args, python_script, python_script_target_name, depends
741    )
742
743  command_line = impl.get_command_line_from_args(wrapped_args)
744  if capture:
745    command_line += ' > %s' % _location_wrapper(
746        impl.get_relative_gen_dir(output)
747    )
748
749  impl.fprint('genrule(')
750  impl.fprint('  name = "%s",' % custom_target.target_name())
751  impl.fprint('  srcs = [')
752  for src in relative_inputs:
753    impl.fprint('    "%s",' % src)
754  for dep in depends:
755    assert type(dep) is impl.CustomTarget
756    impl.fprint('    ":%s",' % dep.target_name())
757  impl.fprint('  ],')
758  impl.fprint('  outs = [')
759  for out in relative_outputs:
760    impl.fprint('    "%s",' % out)
761  impl.fprint('  ],')
762  if python_script_target_name != '':
763    impl.fprint('  tools = [ "%s" ],' % python_script_target_name)
764  impl.fprint('  cmd = "%s"' % command_line)
765  impl.fprint(')')
766
767  return custom_target
768