Repository: mesonbuild/meson

Issue:
AttributeError: 'CustomTargetIndex' object has no attribute 'is_internal'
**Describe the bug**
meson crashes with the error in the title

**To Reproduce**
```meson
project('bug', 'c')
t = custom_target('dummy', output: [ 'libdummy.a' ], command: [ 'echo', 'dummy' ])
dep = declare_dependency(link_with: t[0])

l = static_library('l', 'src/empty.c', dependencies: dep, install: true)
```

**system parameters**
* ArchLinux
* Python: 3.8.0
* meson: 0.52.1


Discussion:
Same thing happens for 'CustomTarget' object too
Sounds like a regression I introduced in 0.52, can you please try the fix I made in https://github.com/mesonbuild/meson/pull/6366?
@xclaesse I am not sure if #6366 is the right solution. Seems like it will have the problem 484b721 tried to fix. i.e. if a static library is linked with a custom target that is not installed, things won't work.
Maybe we should produce an error `if isinstance(self, StaticLibrary) and isinstance(t, (CustomTarget, CustomTargetIndex)) and not t.need_install`?
BTW, #6366 also doesn't fix the CustomTargetIndex case
+1 this is a blocker (without awful hacks) for linking static libraries generated by external build systems (eg. make) via custom targets. @yshui are you still working on https://github.com/mesonbuild/meson/pull/6367? If not I can pick up where you left off.
@andreimaximov please go ahead.
I'm also blocked by this, @andreimaximov have you made progress on this by any chance ?
@Sahnvour I haven't had a chance to spend much time on this. It seems like implementing `is_internal` for `CustomTarget` and `CustomTargetIndex` is the way to go but I stopped when I hit this:

https://github.com/mesonbuild/meson/blob/e5fabce1f32ffe918a96acd026c0e258f6d682b8/mesonbuild/build.py#L1154-L1159

Say we have...

1. static-lib-custom-target A, not installed
2. static-lib B, links in A, installed

It seems like B would need a way to get all the object files out of A because A is not installed and should thus be considered internal. I might be totally wrong though... maybe @xclaesse can pitch in.
Hello,
this is also a blocker for me 😭 
@andreimaximov  @xclaesse did you have some time to work on this ?
Thank you !

Resolving patch:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -1130,10 +1130,16 @@ def is_internal(self):
 
     def link(self, target):
         for t in unholder(listify(target)):
-            if isinstance(self, StaticLibrary) and self.need_install and t.is_internal():
-                # When we're a static library and we link_with to an
-                # internal/convenience library, promote to link_whole.
-                return self.link_whole(t)
+            if isinstance(self, StaticLibrary) and self.need_install:
+                if isinstance(t, (CustomTarget, CustomTargetIndex)):
+                    if not t.should_install():
+                        mlog.warning('Try to link an installed static library target {} with a custom target '
+                                     'that is not installed, this might cause problems when you try to use '
+                                     'this static library'.format(self.name))
+                elif t.is_internal():
+                    # When we're a static library and we link_with to an
+                    # internal/convenience library, promote to link_whole.
+                    return self.link_whole(t)
             if not isinstance(t, (Target, CustomTargetIndex)):
                 raise InvalidArguments('{!r} is not a target.'.format(t))
             if not t.is_linkable_target():
@@ -2274,6 +2280,18 @@ def get_link_dep_subdirs(self):
     def get_all_link_deps(self):
         return []
 
+    def is_internal(self) -> bool:
+        if not self.should_install():
+            return True
+        for out in self.get_outputs():
+            # Can't check if this is a static library, so try to guess
+            if not out.endswith(('.a', '.lib')):
+                return False
+        return True
+
+    def extract_all_objects_recurse(self):
+        return self.get_outputs()
+
     def type_suffix(self):
         return "@cus"
 
@@ -2419,6 +2437,15 @@ def is_linkable_target(self):
         if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so':
             return True
 
+    def should_install(self) -> bool:
+        return self.target.should_install()
+
+    def is_internal(self) -> bool:
+        return self.target.is_internal()
+
+    def extract_all_objects_recurse(self):
+        return self.target.extract_all_objects_recurse()
+
 class ConfigureFile:
 
     def __init__(self, subdir, sourcename, targetname, configuration_data):


Test patch:
diff --git a/test cases/common/215 link custom/custom_target.c b/test cases/common/215 link custom/custom_target.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/215 link custom/custom_target.c	
@@ -0,0 +1,6 @@
+void outer_lib_func(void);
+
+int main(void) {
+    outer_lib_func();
+    return 0;
+}
diff --git a/test cases/common/215 link custom/custom_target.py b/test cases/common/215 link custom/custom_target.py
new file mode 100644
--- /dev/null
+++ b/test cases/common/215 link custom/custom_target.py	
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import shutil, sys
+
+if __name__ == '__main__':
+    shutil.copyfile(sys.argv[1], sys.argv[2])
diff --git a/test cases/common/215 link custom/dummy.c b/test cases/common/215 link custom/dummy.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/215 link custom/dummy.c	
@@ -0,0 +1 @@
+void inner_lib_func(void) {}
\ No newline at end of file
diff --git a/test cases/common/215 link custom/meson.build b/test cases/common/215 link custom/meson.build
--- a/test cases/common/215 link custom/meson.build	
+++ b/test cases/common/215 link custom/meson.build	
@@ -57,3 +57,22 @@ d2_i = declare_dependency(link_whole: clib[0])
 
 exe4_i = executable('prog4_i', 'prog.c', dependencies: d2_i)
 test('linkwhole2_i', exe2_i)
+
+# Link with custom target
+
+dummy = static_library('dummy', 'dummy.c')
+
+custom_prog = find_program('custom_target.py')
+t = custom_target('custom', input: dummy, output: 'libcustom.a', command: [custom_prog, '@INPUT@', '@OUTPUT@'])
+
+dep1 = declare_dependency(link_with: t)
+dep2 = declare_dependency(link_with: t[0])
+
+lib1 = static_library('lib1', 'outerlib.c', dependencies: dep1)
+lib2 = static_library('lib2', 'outerlib.c', dependencies: dep2)
+
+exe1 = executable('exe1', 'custom_target.c', link_with: lib1)
+test('custom_target_1', exe1)
+
+exe1_2 = executable('exe1_2', 'custom_target.c', link_with: lib2)
+test('custom_target_2', exe2)
\ No newline at end of file
diff --git a/test cases/common/215 link custom/outerlib.c b/test cases/common/215 link custom/outerlib.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/215 link custom/outerlib.c	
@@ -0,0 +1,3 @@
+void inner_lib_func(void);
+
+void outer_lib_func(void) { inner_lib_func(); }
\ No newline at end of file