Propagate user link flags from `deps` on `objc_library` CcInfo provider

Currently `objc_library` propagates frameworks to the CcInfo provider,
based on the target's built ObjC provider `sdk_frameworks` field. This
change aims to propagate both dylibs and frameworks from both the
ObjC provider, and merged `deps` CcInfo's.

PiperOrigin-RevId: 438387102
diff --git a/src/main/starlark/builtins_bzl/common/objc/objc_library.bzl b/src/main/starlark/builtins_bzl/common/objc/objc_library.bzl
index 9252d40..bed58dd 100644
--- a/src/main/starlark/builtins_bzl/common/objc/objc_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/objc/objc_library.bzl
@@ -26,9 +26,6 @@
 coverage_common = _builtins.toplevel.coverage_common
 apple_common = _builtins.toplevel.apple_common
 
-def _rule_error(msg):
-    fail(msg)
-
 def _attribute_error(attr_name, msg):
     fail("in attribute '" + attr_name + "': " + msg)
 
@@ -71,10 +68,10 @@
 
     libraries.extend(objc_provider.cc_library.to_list())
 
-    sdk_frameworks = objc_provider.sdk_framework.to_list()
-    user_link_flags = []
-    for sdk_framework in sdk_frameworks:
-        user_link_flags.append(["-framework", sdk_framework])
+    user_link_flags = _user_link_flags(
+        cc_info = merged_objc_library_cc_infos,
+        objc_provider = objc_provider,
+    )
 
     direct_linker_inputs = []
     if len(user_link_flags) != 0 or len(libraries) != 0 or objc_provider.linkstamp:
@@ -106,6 +103,41 @@
         alwayslink = alwayslink,
     )
 
+def _user_link_flags(*, cc_info, objc_provider):
+    """Builds objc_library CcInfo user link flags for frameworks and dylibs.
+
+    Args:
+        cc_info: Merged CcInfo provider from objc_library target deps.
+        objc_provider: Current objc_library ObjC provider.
+    Returns:
+        List of user link flags for frameworks and dylibs.
+    """
+
+    sdk_dylibs = objc_provider.sdk_dylib.to_list()
+    sdk_frameworks = objc_provider.sdk_framework.to_list()
+
+    all_user_link_flags = []
+    all_user_link_flags.extend(objc_provider.linkopt.to_list())
+
+    for linker_input in cc_info.linking_context.linker_inputs.to_list():
+        all_user_link_flags.extend(linker_input.user_link_flags)
+
+    for i, user_link_flag in enumerate(all_user_link_flags):
+        if user_link_flag.startswith("-l"):
+            sdk_dylibs.append("lib" + user_link_flag[2:])
+        elif user_link_flag == "-framework":
+            sdk_frameworks.append(all_user_link_flags[i + 1])
+
+    sdk_user_link_flags = []
+    for sdk_framework in depset(sdk_frameworks).to_list():
+        sdk_user_link_flags.append(["-framework", sdk_framework])
+    for sdk_dylib in depset(sdk_dylibs).to_list():
+        if sdk_dylib.startswith("lib"):
+            sdk_dylib = sdk_dylib[3:]
+        sdk_user_link_flags.append(["-l" + sdk_dylib])
+
+    return sdk_user_link_flags
+
 def _objc_library_impl(ctx):
     _validate_attributes(ctx)
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java
index c54a916..ecef140 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcLibraryTest.java
@@ -53,6 +53,7 @@
 import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.analysis.util.ScratchAttributeWriter;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
@@ -2444,4 +2445,96 @@
 
     assertThat(instrumentedFilesInfo.getCoverageSupportFiles().toList()).isEmpty();
   }
+
+  private ImmutableList<String> getCcInfoUserLinkFlagsFromTarget(String target)
+      throws LabelSyntaxException {
+    return getConfiguredTarget(target)
+        .get(CcInfo.PROVIDER)
+        .getCcLinkingContext()
+        .getUserLinkFlags()
+        .toList()
+        .stream()
+        .map(CcLinkingContext.LinkOptions::get)
+        .flatMap(List::stream)
+        .collect(toImmutableList());
+  }
+
+  @Test
+  public void testSdkUserLinkFlagsFromSdkFieldsAndLinkoptsArePropagatedOnCcInfo() throws Exception {
+    scratch.file(
+        "x/BUILD",
+        "objc_library(",
+        "    name = 'foo',",
+        "    linkopts = [",
+        "        '-lxml2',",
+        "        '-framework AVFoundation',",
+        "    ],",
+        "    sdk_dylibs = ['libz'],",
+        "    sdk_frameworks = ['CoreData'],",
+        "    deps = [':bar', ':car'],",
+        ")",
+        "objc_library(",
+        "    name = 'bar',",
+        "    linkopts = [",
+        "        '-lsqlite3',",
+        "    ],",
+        "    sdk_frameworks = ['Foundation'],",
+        ")",
+        "objc_library(",
+        "    name = 'car',",
+        "    linkopts = [",
+        "        '-framework UIKit',",
+        "    ],",
+        "    sdk_dylibs = ['libc++'],",
+        ")");
+
+    ImmutableList<String> userLinkFlags = getCcInfoUserLinkFlagsFromTarget("//x:foo");
+    assertThat(userLinkFlags).isNotEmpty();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "AVFoundation").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "CoreData").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "Foundation").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "UIKit").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-lz", "-lc++", "-lxml2", "-lsqlite3");
+  }
+
+  @Test
+  public void testNoDuplicateSdkUserLinkFlagsFromMultipleDepsOnCcInfo() throws Exception {
+    scratch.file(
+        "x/BUILD",
+        "objc_library(",
+        "    name = 'foo',",
+        "    linkopts = [",
+        "        '-lsqlite3',",
+        "        '-framework UIKit',",
+        "    ],",
+        "    sdk_dylibs = ['libc++'],",
+        "    sdk_frameworks = ['Foundation'],",
+        "    deps = [':bar', ':car'],",
+        ")",
+        "objc_library(",
+        "    name = 'bar',",
+        "    linkopts = [",
+        "        '-lsqlite3',",
+        "        '-framework CoreData',",
+        "    ],",
+        "    sdk_frameworks = ['Foundation'],",
+        ")",
+        "objc_library(",
+        "    name = 'car',",
+        "    linkopts = [",
+        "        '-framework UIKit',",
+        "        '-framework CoreData',",
+        "    ],",
+        "    sdk_dylibs = ['libc++'],",
+        ")");
+    ImmutableList<String> userLinkFlags = getCcInfoUserLinkFlagsFromTarget("//x:foo");
+    assertThat(userLinkFlags).isNotEmpty();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "CoreData").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "Foundation").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-framework", "UIKit").inOrder();
+    assertThat(userLinkFlags).containsAtLeast("-lc++", "-lsqlite3");
+    ImmutableList<String> userLinkFlagsWithoutFrameworkFlags =
+        userLinkFlags.stream().filter(s -> !s.equals("-framework")).collect(toImmutableList());
+    assertThat(userLinkFlagsWithoutFrameworkFlags).containsNoDuplicates();
+  }
 }