Merge pull request #11737 from amyspark/amyspark/add-nasm-building-rules-to-xcode

backends: Add Nasm build rules to Xcode
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index af730f8..0db5c79 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -2013,6 +2013,34 @@
             env.prepend('PATH', list(extra_paths))
         return env
 
+    def compiler_to_generator_args(self, target: build.BuildTarget,
+                                   compiler: 'Compiler', output: str = '@OUTPUT@',
+                                   depfile: T.Union[str, None] = '@DEPFILE@',
+                                   extras: T.Union[T.List[str], None] = None,
+                                   input: str = '@INPUT@') -> CompilerArgs:
+        '''
+        The VS and Xcode backends need the full set of arguments for making a
+        custom build rule. This is a convenience method to convert a Compiler
+        to its arguments, for later concatenation.
+        '''
+        # FIXME: There are many other args missing
+        commands = self.generate_basic_compiler_args(target, compiler)
+        if depfile:
+            commands += compiler.get_dependency_gen_args(output, depfile)
+        commands += compiler.get_output_args(output)
+        commands += self.get_source_dir_include_args(target, compiler)
+        commands += self.get_build_dir_include_args(target, compiler)
+        commands += compiler.get_compile_only_args()
+        # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these
+        # near the end since these are supposed to override everything else.
+        commands += self.escape_extra_args(target.get_extra_args(compiler.get_language()))
+        # Do not escape this one, it is interpreted by the build system
+        # (Xcode considers these as variables to expand at build time)
+        if extras is not None:
+            commands += extras
+        commands += [input]
+        return commands
+
     def compiler_to_generator(self, target: build.BuildTarget,
                               compiler: 'Compiler',
                               sources: _ALL_SOURCES_TYPE,
@@ -2026,16 +2054,7 @@
         exelist = compiler.get_exelist()
         exe = programs.ExternalProgram(exelist[0])
         args = exelist[1:]
-        # FIXME: There are many other args missing
-        commands = self.generate_basic_compiler_args(target, compiler)
-        commands += compiler.get_dependency_gen_args('@OUTPUT@', '@DEPFILE@')
-        commands += compiler.get_output_args('@OUTPUT@')
-        commands += compiler.get_compile_only_args() + ['@INPUT@']
-        commands += self.get_source_dir_include_args(target, compiler)
-        commands += self.get_build_dir_include_args(target, compiler)
-        # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these
-        # near the end since these are supposed to override everything else.
-        commands += self.escape_extra_args(target.get_extra_args(compiler.get_language()))
+        commands = self.compiler_to_generator_args(target, compiler)
         generator = build.Generator(exe, args + commands.to_native(),
                                     [output_templ], depfile='@PLAINNAME@.d',
                                     depends=depends)
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index abdac0c..467cd1a 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -10,9 +10,12 @@
 from .. import build
 from .. import mesonlib
 from .. import mlog
+from ..arglist import CompilerArgs
 from ..mesonlib import MesonBugException, MesonException, OptionKey
 
 if T.TYPE_CHECKING:
+    from ..build import BuildTarget
+    from ..compilers import Compiler
     from ..interpreter import Interpreter
 
 INDENT = '\t'
@@ -33,10 +36,12 @@
                 'dylib': 'compiled.mach-o.dylib',
                 'o': 'compiled.mach-o.objfile',
                 's': 'sourcecode.asm',
-                'asm': 'sourcecode.asm',
+                'asm': 'sourcecode.nasm',
                 'metal': 'sourcecode.metal',
                 'glsl': 'sourcecode.glsl',
                 }
+NEEDS_CUSTOM_RULES = {'nasm': 'sourcecode.nasm',
+                      }
 LANGNAMEMAP = {'c': 'C',
                'cpp': 'CPLUSPLUS',
                'objc': 'OBJC',
@@ -271,6 +276,7 @@
         self.generate_native_target_map()
         self.generate_native_frameworks_map()
         self.generate_custom_target_map()
+        self.generate_native_target_build_rules_map()
         self.generate_generator_target_map()
         self.generate_source_phase_map()
         self.generate_target_dependency_map()
@@ -288,6 +294,9 @@
         objects_dict.add_comment(PbxComment('Begin PBXBuildFile section'))
         self.generate_pbx_build_file(objects_dict)
         objects_dict.add_comment(PbxComment('End PBXBuildFile section'))
+        objects_dict.add_comment(PbxComment('Begin PBXBuildRule section'))
+        self.generate_pbx_build_rule(objects_dict)
+        objects_dict.add_comment(PbxComment('End PBXBuildRule section'))
         objects_dict.add_comment(PbxComment('Begin PBXBuildStyle section'))
         self.generate_pbx_build_style(objects_dict)
         objects_dict.add_comment(PbxComment('End PBXBuildStyle section'))
@@ -401,6 +410,16 @@
         for t in self.build_targets:
             self.native_targets[t] = self.gen_id()
 
+    def generate_native_target_build_rules_map(self) -> None:
+        self.build_rules = {}
+        for name, target in self.build_targets.items():
+            languages = {}
+            for language in target.compilers:
+                if language not in NEEDS_CUSTOM_RULES:
+                    continue
+                languages[language] = self.gen_id()
+            self.build_rules[name] = languages
+
     def generate_custom_target_map(self) -> None:
         self.shell_targets = {}
         self.custom_target_output_buildfile = {}
@@ -720,6 +739,53 @@
             settings_dict.add_item('COPY_PHASE_STRIP', 'NO')
             styledict.add_item('name', f'"{name}"')
 
+    def to_shell_script(self, args: CompilerArgs) -> str:
+        quoted_cmd = []
+        for c in args:
+            quoted_cmd.append(c.replace('"', chr(92) + '"'))
+        cmd = ' '.join(quoted_cmd)
+        return f"\"#!/bin/sh\\n{cmd}\\n\""
+
+    def generate_pbx_build_rule(self, objects_dict: PbxDict) -> None:
+        for name, languages in self.build_rules.items():
+            target: BuildTarget = self.build_targets[name]
+            for language, idval in languages.items():
+                compiler: Compiler = target.compilers[language]
+                buildrule = PbxDict()
+                buildrule.add_item('isa', 'PBXBuildRule')
+                buildrule.add_item('compilerSpec', 'com.apple.compilers.proxy.script')
+                if compiler.get_id() != 'yasm':
+                    # Yasm doesn't generate escaped build rules
+                    buildrule.add_item('dependencyFile', '"$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).d"')
+                buildrule.add_item('fileType', NEEDS_CUSTOM_RULES[language])
+                inputfiles = PbxArray()
+                buildrule.add_item('inputFiles', inputfiles)
+                buildrule.add_item('isEditable', '0')
+                outputfiles = PbxArray()
+                outputfiles.add_item('"$(DERIVED_FILE_DIR)/$(INPUT_FILE_BASE).o"')
+                buildrule.add_item('outputFiles', outputfiles)
+                # Do NOT use this parameter. Xcode will accept it from the UI,
+                # but the parser will break down inconsistently upon next
+                # opening. rdar://FB12144055
+                # outputargs = PbxArray()
+                # args = self.generate_basic_compiler_args(target, compiler)
+                # outputargs.add_item(self.to_shell_script(args))
+                # buildrule.add_item('outputFilesCompilerFlags', outputargs)
+                commands = CompilerArgs(compiler)
+                commands += compiler.get_exelist()
+                if compiler.get_id() == 'yasm':
+                    # Yasm doesn't generate escaped build rules
+                    commands += self.compiler_to_generator_args(target, compiler, output='"$SCRIPT_OUTPUT_FILE_0"', input='"$SCRIPT_INPUT_FILE"', depfile=None)
+                else:
+                    commands += self.compiler_to_generator_args(target,
+                                                                compiler,
+                                                                output='"$SCRIPT_OUTPUT_FILE_0"',
+                                                                input='"$SCRIPT_INPUT_FILE"',
+                                                                depfile='"$(dirname "$SCRIPT_OUTPUT_FILE_0")/$(basename "$SCRIPT_OUTPUT_FILE_0" .o).d"',
+                                                                extras=['$OTHER_INPUT_FILE_FLAGS'])
+                buildrule.add_item('script', self.to_shell_script(commands))
+                objects_dict.add_item(idval, buildrule, 'PBXBuildRule')
+
     def generate_pbx_container_item_proxy(self, objects_dict: PbxDict) -> None:
         for t in self.build_targets:
             proxy_dict = PbxDict()
@@ -1115,7 +1181,10 @@
                     generator_id += 1
             for bpname, bpval in t.buildphasemap.items():
                 buildphases_array.add_item(bpval, f'{bpname} yyy')
-            ntarget_dict.add_item('buildRules', PbxArray())
+            build_rules = PbxArray()
+            for language, build_rule_idval in self.build_rules[tname].items():
+                build_rules.add_item(build_rule_idval, f'{language}')
+            ntarget_dict.add_item('buildRules', build_rules)
             dep_array = PbxArray()
             ntarget_dict.add_item('dependencies', dep_array)
             dep_array.add_item(self.regen_dependency_id)
diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py
index d04fbd2..bfe436b 100644
--- a/mesonbuild/compilers/asm.py
+++ b/mesonbuild/compilers/asm.py
@@ -42,8 +42,10 @@
                  linker: T.Optional['DynamicLinker'] = None,
                  full_version: T.Optional[str] = None, is_cross: bool = False):
         super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross)
+        self.links_with_msvc = False
         if 'link' in self.linker.id:
             self.base_options.add(OptionKey('b_vscrt'))
+            self.links_with_msvc = True
 
     def needs_static_linker(self) -> bool:
         return True
@@ -83,9 +85,7 @@
 
     def get_debug_args(self, is_debug: bool) -> T.List[str]:
         if is_debug:
-            if self.info.is_windows():
-                return []
-            return ['-g', '-F', 'dwarf']
+            return ['-g']
         return []
 
     def get_depfile_suffix(self) -> str:
@@ -138,9 +138,12 @@
 
     def get_debug_args(self, is_debug: bool) -> T.List[str]:
         if is_debug:
-            if self.info.is_windows():
+            if self.info.is_windows() and self.links_with_msvc:
+                return ['-g', 'cv8']
+            elif self.info.is_darwin():
                 return ['-g', 'null']
-            return ['-g', 'dwarf2']
+            else:
+                return ['-g', 'dwarf2']
         return []
 
     def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
diff --git a/test cases/nasm/1 configure file/meson.build b/test cases/nasm/1 configure file/meson.build
index 85ecaf1..fac46a6 100644
--- a/test cases/nasm/1 configure file/meson.build
+++ b/test cases/nasm/1 configure file/meson.build
@@ -1,15 +1,17 @@
 project('nasm config file', 'c')
 
-if host_machine.cpu_family() == 'x86' and host_machine.system() == 'windows'
-  asm_format = 'win32'
-elif host_machine.cpu_family() == 'x86_64' and host_machine.system() == 'windows'
-  asm_format = 'win64'
-elif host_machine.cpu_family() == 'x86' and host_machine.system() == 'linux'
+if not host_machine.cpu_family().startswith('x86')
+  error('MESON_SKIP_TEST: nasm only supported for x86 and x86_64')
+endif
+
+if host_machine.system() != 'linux'
+  error('MESON_SKIP_TEST: this test asm is made for Linux')
+endif
+
+if host_machine.cpu_family() == 'x86'
   asm_format = 'elf32'
-elif host_machine.cpu_family() == 'x86_64' and host_machine.system() == 'linux'
-  asm_format = 'elf64'
 else
-  error('MESON_SKIP_TEST: skipping test on this platform')
+  asm_format = 'elf64'
 endif
 
 nasm = find_program('nasm', required: false)
diff --git a/test cases/nasm/2 asm language/meson.build b/test cases/nasm/2 asm language/meson.build
index d025d43..d5a2ba3 100644
--- a/test cases/nasm/2 asm language/meson.build
+++ b/test cases/nasm/2 asm language/meson.build
@@ -5,10 +5,8 @@
   error('MESON_SKIP_TEST: nasm only supported for x86 and x86_64')
 endif
 
-if host_machine.system() == 'windows'
-  error('MESON_SKIP_TEST: this test asm is not made for Windows')
-elif host_machine.system() == 'sunos'
-  error('MESON_SKIP_TEST: this test asm is not made for Solaris or illumos')
+if host_machine.system() != 'linux'
+  error('MESON_SKIP_TEST: this test asm is made for Linux')
 endif
 
 if meson.backend().startswith('vs')
diff --git a/test cases/nasm/3 nasm only/meson.build b/test cases/nasm/3 nasm only/meson.build
index 9777291..18b980d 100644
--- a/test cases/nasm/3 nasm only/meson.build
+++ b/test cases/nasm/3 nasm only/meson.build
@@ -4,6 +4,10 @@
   error('MESON_SKIP_TEST: nasm not found')
 endif
 
+if not ['linux', 'windows'].contains(host_machine.system())
+  error('MESON_SKIP_TEST: this test asm is made for Windows and Linux')
+endif
+
 if meson.backend().startswith('vs')
   error('MESON_SKIP_TEST: VS backend does not recognise NASM yet')
 endif
diff --git a/test cases/nasm/4 through configure/dummy.asm.in b/test cases/nasm/4 through configure/dummy.asm.in
new file mode 100644
index 0000000..5be150e
--- /dev/null
+++ b/test cases/nasm/4 through configure/dummy.asm.in
@@ -0,0 +1,4 @@
+global dummy
+section .rodata align=16
+dummy:
+  dd 0x00010203
diff --git a/test cases/nasm/4 through configure/dummy.def b/test cases/nasm/4 through configure/dummy.def
new file mode 100644
index 0000000..8f8eb99
--- /dev/null
+++ b/test cases/nasm/4 through configure/dummy.def
@@ -0,0 +1,2 @@
+EXPORTS
+	dummy
diff --git a/test cases/nasm/4 through configure/meson.build b/test cases/nasm/4 through configure/meson.build
new file mode 100644
index 0000000..373810f
--- /dev/null
+++ b/test cases/nasm/4 through configure/meson.build
@@ -0,0 +1,30 @@
+project('through configure')
+
+if not add_languages('nasm', required: false)
+  error('MESON_SKIP_TEST: nasm not found')
+endif
+
+if not host_machine.cpu_family().startswith('x86')
+  assert(not add_languages('nasm', required: false))
+  error('MESON_SKIP_TEST: nasm only supported for x86 and x86_64')
+endif
+
+if meson.backend().startswith('vs')
+  error('MESON_SKIP_TEST: VS backend does not recognise NASM yet')
+endif
+
+section = host_machine.system() == 'macos' ? '.rodata' : '.rdata'  
+
+sources = configure_file(
+  input: 'dummy.asm.in',
+  output: 'dummy.asm',
+  configuration: {
+    'section': section
+  }
+)
+
+dummy = library(
+    'dummy',
+    sources,
+    vs_module_defs: 'dummy.def',
+)