void cmGlobalXCodeGenerator::AddDependAndLinkInformation()

in Source/cmGlobalXCodeGenerator.cxx [3589:4161]


void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target)
{
  cmGeneratorTarget* gt = target->GetTarget();
  if (!gt) {
    cmSystemTools::Error("Error no target on xobject\n");
    return;
  }
  if (!gt->IsInBuildSystem()) {
    return;
  }

  // Add dependencies on other CMake targets.
  for (auto const& dep : this->GetTargetDirectDepends(gt)) {
    if (cmXCodeObject* dptarget = this->FindXCodeTarget(dep)) {
      this->AddDependTarget(target, dptarget);
    }
  }

  // Separate libraries into ones that can be linked using "Link Binary With
  // Libraries" build phase and the ones that can't. Only targets that build
  // Apple bundles (.app, .framework, .bundle), executables and dylibs can use
  // this feature and only targets that represent actual libraries (object,
  // static, dynamic or bundle, excluding executables) will be used. These are
  // limitations imposed by CMake use-cases - otherwise a lot of things break.
  // The rest will be linked using linker flags (OTHER_LDFLAGS setting in Xcode
  // project).
  std::map<std::string, std::vector<cmComputeLinkInformation::Item const*>>
    configItemMap;
  auto addToLinkerArguments =
    [&configItemMap](std::string const& configName,
                     cmComputeLinkInformation::Item const* libItemPtr) {
      auto& linkVector = configItemMap[configName];
      if (std::find_if(linkVector.begin(), linkVector.end(),
                       [libItemPtr](cmComputeLinkInformation::Item const* p) {
                         return p == libItemPtr;
                       }) == linkVector.end()) {
        linkVector.push_back(libItemPtr);
      }
    };
  std::vector<cmComputeLinkInformation::Item const*> linkPhaseTargetVector;
  std::map<std::string, std::vector<std::string>> targetConfigMap;
  using ConfigItemPair =
    std::pair<std::string, cmComputeLinkInformation::Item const*>;
  std::map<std::string, std::vector<ConfigItemPair>> targetItemMap;
  std::map<std::string, std::vector<std::string>> targetProductNameMap;
  bool useLinkPhase = false;
  bool forceLinkPhase = false;
  cmValue prop =
    target->GetTarget()->GetProperty("XCODE_LINK_BUILD_PHASE_MODE");
  if (prop) {
    if (*prop == "BUILT_ONLY"_s) {
      useLinkPhase = true;
    } else if (*prop == "KNOWN_LOCATION"_s) {
      useLinkPhase = true;
      forceLinkPhase = true;
    } else if (*prop != "NONE"_s) {
      cmSystemTools::Error(
        cmStrCat("Invalid value for XCODE_LINK_BUILD_PHASE_MODE: ", *prop));
      return;
    }
  }
  for (auto const& configName : this->CurrentConfigurationTypes) {
    cmComputeLinkInformation* cli = gt->GetLinkInformation(configName);
    if (!cli) {
      continue;
    }
    for (auto const& libItem : cli->GetItems()) {
      // Explicitly ignore OBJECT libraries as Xcode emulates them as static
      // libraries without an artifact. Avoid exposing this to the rest of
      // CMake's compilation model.
      if (libItem.Target &&
          libItem.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
        continue;
      }
      // We want to put only static libraries, dynamic libraries, frameworks
      // and bundles that are built from targets that are not imported in "Link
      // Binary With Libraries" build phase. Except if the target property
      // XCODE_LINK_BUILD_PHASE_MODE is KNOWN_LOCATION then all imported and
      // non-target libraries will be added as well.
      if (useLinkPhase &&
          (gt->GetType() == cmStateEnums::EXECUTABLE ||
           gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
           gt->GetType() == cmStateEnums::MODULE_LIBRARY) &&
          ((libItem.Target &&
            (!libItem.Target->IsImported() || forceLinkPhase) &&
            (libItem.Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
             libItem.Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
             libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
             libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) ||
           (!libItem.Target &&
            libItem.IsPath == cmComputeLinkInformation::ItemIsPath::Yes &&
            forceLinkPhase))) {
        std::string libName;
        bool canUseLinkPhase = !libItem.HasFeature() ||
          libItem.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s ||
          libItem.GetFeatureName() == "FRAMEWORK"_s ||
          libItem.GetFeatureName() == "__CMAKE_LINK_XCFRAMEWORK"_s ||
          libItem.GetFeatureName() == "XCFRAMEWORK"_s ||
          libItem.GetFeatureName() == "WEAK_FRAMEWORK"_s ||
          libItem.GetFeatureName() == "WEAK_LIBRARY"_s;
        if (canUseLinkPhase) {
          if (libItem.Target) {
            if (libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY) {
              canUseLinkPhase = canUseLinkPhase && forceLinkPhase;
            } else {
              // If a library target uses custom build output directory Xcode
              // won't pick it up so we have to resort back to linker flags,
              // but that's OK as long as the custom output dir is absolute
              // path.
              for (auto const& libConfigName :
                   this->CurrentConfigurationTypes) {
                canUseLinkPhase = canUseLinkPhase &&
                  libItem.Target->UsesDefaultOutputDir(
                    libConfigName, cmStateEnums::RuntimeBinaryArtifact);
              }
            }
            libName = libItem.Target->GetName();
          } else {
            libName = cmSystemTools::GetFilenameName(libItem.Value.Value);
            // We don't want all the possible files here, just standard
            // libraries
            auto const libExt = cmSystemTools::GetFilenameExtension(libName);
            if (!IsLinkPhaseLibraryExtension(libExt)) {
              canUseLinkPhase = false;
            }
          }
        }
        if (canUseLinkPhase) {
          // Add unique configuration name to target-config map for later
          // checks
          auto& configVector = targetConfigMap[libName];
          if (std::find(configVector.begin(), configVector.end(),
                        configName) == configVector.end()) {
            configVector.push_back(configName);
          }
          // Add a pair of config and item to target-item map
          auto& itemVector = targetItemMap[libName];
          itemVector.emplace_back(configName, &libItem);
          // Add product file-name to a lib-product map
          auto productName =
            cmSystemTools::GetFilenameName(libItem.Value.Value);
          auto& productVector = targetProductNameMap[libName];
          if (std::find(productVector.begin(), productVector.end(),
                        productName) == productVector.end()) {
            productVector.push_back(productName);
          }
          continue;
        }
      }
      // Add this library item to a regular linker flag list
      addToLinkerArguments(configName, &libItem);
    }
  }

  // Go through target library map and separate libraries that are linked
  // in all configurations and produce only single product, from the rest.
  // Only these will be linked through "Link Binary With Libraries" build
  // phase.
  for (auto const& targetLibConfigs : targetConfigMap) {
    // Add this library to "Link Binary With Libraries" build phase if it's
    // linked in all configurations and it has only one product name
    auto& itemVector = targetItemMap[targetLibConfigs.first];
    auto& productVector = targetProductNameMap[targetLibConfigs.first];
    if (targetLibConfigs.second == this->CurrentConfigurationTypes &&
        productVector.size() == 1) {
      // Add this library to "Link Binary With Libraries" list
      linkPhaseTargetVector.push_back(itemVector[0].second);
    } else {
      for (auto const& libItem : targetItemMap[targetLibConfigs.first]) {
        // Add this library item to a regular linker flag list
        addToLinkerArguments(libItem.first, libItem.second);
      }
    }
  }

  // Add libraries to "Link Binary With Libraries" build phase and collect
  // their search paths. Xcode does not support per-configuration linking
  // in this build phase so we don't have to do this for each configuration
  // separately.
  std::vector<std::string> linkSearchPaths;
  std::vector<std::string> frameworkSearchPaths;
  std::set<std::pair<cmXCodeObject*, std::string>> linkBuildFileSet;
  for (auto const& libItem : linkPhaseTargetVector) {
    // Add target output directory as a library search path
    std::string linkDir;
    if (libItem->Target) {
      linkDir = libItem->Target->GetLocationForBuild();
    } else {
      linkDir = libItem->Value.Value;
    }
    if (cmHasSuffix(libItem->GetFeatureName(), "FRAMEWORK"_s)) {
      auto fwDescriptor = this->SplitFrameworkPath(
        linkDir, cmGlobalGenerator::FrameworkFormat::Extended);
      if (fwDescriptor && !fwDescriptor->Directory.empty()) {
        linkDir = fwDescriptor->Directory;
        if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(),
                      linkDir) == frameworkSearchPaths.end()) {
          frameworkSearchPaths.push_back(linkDir);
        }
      }
    } else {
      linkDir = cmSystemTools::GetParentDirectory(linkDir);
      if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) ==
          linkSearchPaths.end()) {
        linkSearchPaths.push_back(linkDir);
      }
    }

    if (libItem->Target && !libItem->Target->IsImported()) {
      for (auto const& configName : this->CurrentConfigurationTypes) {
        target->AddDependTarget(configName, libItem->Target->GetName());
      }
    }
    // Get the library target
    auto* libTarget = FindXCodeTarget(libItem->Target);
    cmXCodeObject* buildFile;
    if (!libTarget) {
      if (libItem->IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
        // Get or create a direct file ref in the root project
        auto cleanPath = libItem->Value.Value;
        if (cmSystemTools::FileIsFullPath(cleanPath)) {
          // Some arguments are reported as paths, but they are actually not,
          // so we can't collapse them, and neither can we collapse relative
          // paths
          cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
        }
        auto it = this->ExternalLibRefs.find(cleanPath);
        if (it == this->ExternalLibRefs.end()) {
          buildFile = CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr);
          if (!buildFile) {
            // Add this library item back to a regular linker flag list
            for (auto const& conf : configItemMap) {
              addToLinkerArguments(conf.first, libItem);
            }
            continue;
          }
          this->ExternalLibRefs.emplace(cleanPath, buildFile);
        } else {
          buildFile = it->second;
        }
      } else {
        // Add this library item back to a regular linker flag list
        for (auto const& conf : configItemMap) {
          addToLinkerArguments(conf.first, libItem);
        }
        continue;
      }
    } else {
      // Add the target output file as a build reference for other targets
      // to link against
      auto* fileRefObject = libTarget->GetAttribute("productReference");
      if (!fileRefObject) {
        // Add this library item back to a regular linker flag list
        for (auto const& conf : configItemMap) {
          addToLinkerArguments(conf.first, libItem);
        }
        continue;
      }
      auto it = FileRefToBuildFileMap.find(fileRefObject);
      if (it == FileRefToBuildFileMap.end()) {
        buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile);
        buildFile->AddAttribute("fileRef", fileRefObject);
        FileRefToBuildFileMap[fileRefObject] = buildFile;
      } else {
        buildFile = it->second;
      }
    }
    // Add this reference to current target
    auto* buildPhases = target->GetAttribute("buildPhases");
    if (!buildPhases) {
      cmSystemTools::Error("Missing buildPhase of target");
      continue;
    }
    auto* frameworkBuildPhase =
      buildPhases->GetObject(cmXCodeObject::PBXFrameworksBuildPhase);
    if (!frameworkBuildPhase) {
      cmSystemTools::Error("Missing PBXFrameworksBuildPhase of buildPhase");
      continue;
    }
    auto* buildFiles = frameworkBuildPhase->GetAttribute("files");
    if (!buildFiles) {
      cmSystemTools::Error("Missing files of PBXFrameworksBuildPhase");
      continue;
    }
    if (buildFile) {
      if (cmHasPrefix(libItem->GetFeatureName(), "WEAK_"_s)) {
        auto key = std::make_pair(buildFile->GetAttribute("fileRef"),
                                  libItem->GetFeatureName());
        if (linkBuildFileSet.find(key) != linkBuildFileSet.end()) {
          continue;
        }
        linkBuildFileSet.insert(key);

        cmXCodeObject* buildObject =
          this->CreateObject(cmXCodeObject::PBXBuildFile);
        buildObject->AddAttribute("fileRef", key.first);
        // Add settings, ATTRIBUTES, Weak flag
        cmXCodeObject* settings =
          this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP);
        cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST);
        attrs->AddObject(this->CreateString("Weak"));
        settings->AddAttribute("ATTRIBUTES", attrs);
        buildObject->AddAttribute("settings", settings);
        buildFile = buildObject;
      }
      if (!buildFiles->HasObject(buildFile)) {
        buildFiles->AddObject(buildFile);
      }
    }
  }

  // Loop over configuration types and set per-configuration info.
  for (auto const& configName : this->CurrentConfigurationTypes) {
    {
      // Add object library contents as link flags.
      BuildObjectListOrString libSearchPaths(this, true);
      std::vector<cmSourceFile const*> objs;
      gt->GetExternalObjects(objs, configName);
      for (auto const* sourceFile : objs) {
        if (sourceFile->GetObjectLibrary().empty()) {
          continue;
        }
        libSearchPaths.Add(this->XCodeEscapePath(sourceFile->GetFullPath()));
      }
      this->AppendBuildSettingAttribute(
        target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(),
        configName);
    }

    // Compute the link library and directory information.
    cmComputeLinkInformation* cli = gt->GetLinkInformation(configName);
    if (!cli) {
      continue;
    }

    // add .xcframework include paths
    {
      // Keep track of framework search paths we've already added or that are
      // part of the set of implicit search paths. We don't want to repeat
      // them and we also need to avoid hard-coding any SDK-specific paths.
      // This is essential for getting device-and-simulator builds to work,
      // otherwise we end up hard-coding a path to the wrong SDK for
      // SDK-provided frameworks that are added by their full path.
      std::set<std::string> emitted(cli->GetFrameworkPathsEmitted());
      BuildObjectListOrString includePaths(this, true);
      BuildObjectListOrString fwSearchPaths(this, true);
      for (auto const& libItem : configItemMap[configName]) {
        auto const& libName = *libItem;
        if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
          auto cleanPath = libName.Value.Value;
          if (cmSystemTools::FileIsFullPath(cleanPath)) {
            cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
          }
          bool isXcFramework =
            cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s);
          if (isXcFramework) {
            auto plist = cmParseXcFrameworkPlist(
              cleanPath, *this->Makefiles.front(), libName.Value.Backtrace);
            if (!plist) {
              return;
            }
            if (auto const* library = plist->SelectSuitableLibrary(
                  *this->Makefiles.front(), libName.Value.Backtrace)) {
              auto libraryPath =
                cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
                         library->LibraryPath);
              if (auto const fwDescriptor = this->SplitFrameworkPath(
                    libraryPath,
                    cmGlobalGenerator::FrameworkFormat::Relaxed)) {
                if (!fwDescriptor->Directory.empty() &&
                    emitted.insert(fwDescriptor->Directory).second) {
                  // This is a search path we had not added before and it
                  // isn't an implicit search path, so we need it
                  fwSearchPaths.Add(
                    this->XCodeEscapePath(fwDescriptor->Directory));
                }
              } else {
                if (!library->HeadersPath.empty()) {
                  includePaths.Add(this->XCodeEscapePath(
                    cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
                             library->HeadersPath)));
                }
              }
            } else {
              return;
            }
          }
        }
      }
      if (!includePaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target, "HEADER_SEARCH_PATHS",
                                          includePaths.CreateList(),
                                          configName);
      }
      if (!fwSearchPaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
                                          fwSearchPaths.CreateList(),
                                          configName);
      }
    }

    // Skip link information for object libraries.
    if (gt->GetType() == cmStateEnums::OBJECT_LIBRARY ||
        gt->GetType() == cmStateEnums::STATIC_LIBRARY) {
      continue;
    }

    // Add dependencies directly on library files.
    for (auto const& libDep : cli->GetDepends()) {
      target->AddDependLibrary(configName, libDep);
    }

    // add the library search paths
    {
      BuildObjectListOrString libSearchPaths(this, true);

      for (auto const& libDir : cli->GetDirectories()) {
        if (!libDir.empty() && libDir != "/usr/lib"_s) {
          cmPolicies::PolicyStatus cmp0142 =
            target->GetTarget()->GetPolicyStatusCMP0142();
          if (cmp0142 == cmPolicies::OLD || cmp0142 == cmPolicies::WARN) {
            libSearchPaths.Add(this->XCodeEscapePath(cmStrCat(
              libDir, "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)")));
          }
          libSearchPaths.Add(this->XCodeEscapePath(libDir));
        }
      }

      // Add previously collected paths where to look for libraries
      // that were added to "Link Binary With Libraries"
      for (auto& libDir : linkSearchPaths) {
        libSearchPaths.Add(this->XCodeEscapePath(libDir));
      }

      // Add toolchain specified language link directories
      std::string const& linkDirsString =
        this->Makefiles.front()->GetSafeDefinition(cmStrCat(
          "CMAKE_", cli->GetLinkLanguage(), "_STANDARD_LINK_DIRECTORIES"));

      for (auto const& libDir : cmList(linkDirsString)) {
        libSearchPaths.Add(this->XCodeEscapePath(libDir));
      }

      if (!libSearchPaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS",
                                          libSearchPaths.CreateList(),
                                          configName);
      }
    }

    // add framework search paths
    {
      BuildObjectListOrString fwSearchPaths(this, true);
      // Add previously collected paths where to look for frameworks
      // that were added to "Link Binary With Libraries"
      for (auto& fwDir : frameworkSearchPaths) {
        fwSearchPaths.Add(this->XCodeEscapePath(fwDir));
      }
      if (!fwSearchPaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
                                          fwSearchPaths.CreateList(),
                                          configName);
      }
    }

    // now add the left-over link libraries
    {
      // Keep track of framework search paths we've already added or that are
      // part of the set of implicit search paths. We don't want to repeat
      // them and we also need to avoid hard-coding any SDK-specific paths.
      // This is essential for getting device-and-simulator builds to work,
      // otherwise we end up hard-coding a path to the wrong SDK for
      // SDK-provided frameworks that are added by their full path.
      std::set<std::string> emitted(cli->GetFrameworkPathsEmitted());
      BuildObjectListOrString libPaths(this, true);
      BuildObjectListOrString fwSearchPaths(this, true);
      for (auto const& libItem : configItemMap[configName]) {
        auto const& libName = *libItem;
        if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) {
          auto cleanPath = libName.Value.Value;
          if (cmSystemTools::FileIsFullPath(cleanPath)) {
            cleanPath = cmSystemTools::CollapseFullPath(cleanPath);
          }
          bool isXcFramework =
            cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s);
          bool isFramework = !isXcFramework &&
            cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s);
          if (isFramework) {
            auto const fwDescriptor = this->SplitFrameworkPath(
              cleanPath, cmGlobalGenerator::FrameworkFormat::Extended);
            if (isFramework && !fwDescriptor->Directory.empty() &&
                emitted.insert(fwDescriptor->Directory).second) {
              // This is a search path we had not added before and it isn't
              // an implicit search path, so we need it
              fwSearchPaths.Add(
                this->XCodeEscapePath(fwDescriptor->Directory));
            }
            if (libName.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s) {
              // use the full path
              libPaths.Add(
                libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
                  .Value);
            } else {
              libPaths.Add(libName
                             .GetFormattedItem(this->XCodeEscapePath(
                               fwDescriptor->GetLinkName()))
                             .Value);
            }
          } else if (isXcFramework) {
            auto plist = cmParseXcFrameworkPlist(
              cleanPath, *this->Makefiles.front(), libName.Value.Backtrace);
            if (!plist) {
              return;
            }
            if (auto const* library = plist->SelectSuitableLibrary(
                  *this->Makefiles.front(), libName.Value.Backtrace)) {
              auto libraryPath =
                cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/',
                         library->LibraryPath);
              if (auto const fwDescriptor = this->SplitFrameworkPath(
                    libraryPath,
                    cmGlobalGenerator::FrameworkFormat::Relaxed)) {
                libPaths.Add(cmStrCat(
                  "-framework ",
                  this->XCodeEscapePath(fwDescriptor->GetLinkName())));
              } else {
                libPaths.Add(
                  libName.GetFormattedItem(this->XCodeEscapePath(libraryPath))
                    .Value);
              }
            } else {
              return;
            }
          } else {
            libPaths.Add(
              libName.GetFormattedItem(this->XCodeEscapePath(cleanPath))
                .Value);
          }
          if ((!libName.Target || libName.Target->IsImported()) &&
              (isFramework || isXcFramework ||
               IsLinkPhaseLibraryExtension(cleanPath))) {
            // Create file reference for embedding
            auto it = this->ExternalLibRefs.find(cleanPath);
            if (it == this->ExternalLibRefs.end()) {
              auto* buildFile =
                this->CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr);
              if (buildFile) {
                this->ExternalLibRefs.emplace(cleanPath, buildFile);
              }
            }
          }
        } else if (!libName.Target ||
                   libName.Target->GetType() !=
                     cmStateEnums::INTERFACE_LIBRARY) {
          libPaths.Add(libName.Value.Value);
        }
        if (libName.Target && !libName.Target->IsImported()) {
          target->AddDependTarget(configName, libName.Target->GetName());
        }
      }
      if (!libPaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target,
                                          this->GetTargetLinkFlagsVar(gt),
                                          libPaths.CreateList(), configName);
      }
      if (!fwSearchPaths.IsEmpty()) {
        this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS",
                                          fwSearchPaths.CreateList(),
                                          configName);
      }
    }
  }
}