فهرست منبع

Merge topic 'fortran-objects-as-sources-fix'

beb1393f8f Merge branch 'revert-exact-collation-depends-3.27' into fortran-objects-as-sources-fix
a033dce326 Makefiles: provide, but do not consume, "forward linked" target dirs
7cd0adab1b cmCommonTargetGenerator: use modules from linked object-referenced targets
1175f1c874 LinkItem: track `cmSourceFile` instances for external objects
d2fa56772f Ninja: support "forwarding" modules from other targets
ec1e589bec Ninja: Revert exact collation dependencies for 3.27
06df59b930 cmCommonTargetGenerator: return forward linked target dirs too
f8729ab366 cmLocalUnixMakefileGenerator3: handle object-referencing Fortran modules
...

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !8989
Brad King 1 سال پیش
والد
کامیت
c2bd158712
39فایلهای تغییر یافته به همراه419 افزوده شده و 78 حذف شده
  1. 62 28
      Source/cmCommonTargetGenerator.cxx
  2. 9 2
      Source/cmCommonTargetGenerator.h
  3. 1 0
      Source/cmComputeLinkDepends.cxx
  4. 4 0
      Source/cmComputeLinkDepends.h
  5. 13 13
      Source/cmComputeLinkInformation.cxx
  6. 6 0
      Source/cmComputeLinkInformation.h
  7. 6 0
      Source/cmDependsFortran.cxx
  8. 60 27
      Source/cmGeneratorTarget.cxx
  9. 5 0
      Source/cmGeneratorTarget.h
  10. 58 1
      Source/cmGlobalNinjaGenerator.cxx
  11. 1 0
      Source/cmGlobalNinjaGenerator.h
  12. 4 0
      Source/cmLinkItem.h
  13. 11 0
      Source/cmLocalUnixMakefileGenerator3.cxx
  14. 3 0
      Source/cmMakefile.cxx
  15. 13 2
      Source/cmMakefileTargetGenerator.cxx
  16. 25 5
      Source/cmNinjaTargetGenerator.cxx
  17. 6 0
      Tests/FortranModules/CMakeLists.txt
  18. 7 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/CMakeLists.txt
  19. 3 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/dummy.f90
  20. 1 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/subdir/CMakeLists.txt
  21. 11 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/subdir/obj.f90
  22. 13 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/use.f90
  23. 6 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/CMakeLists.txt
  24. 1 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/subdir/CMakeLists.txt
  25. 11 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/subdir/obj.f90
  26. 13 0
      Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/use.f90
  27. 5 0
      Tests/FortranModules/ModulesViaTargetObjectsLink/CMakeLists.txt
  28. 3 0
      Tests/FortranModules/ModulesViaTargetObjectsLink/dummy.f90
  29. 11 0
      Tests/FortranModules/ModulesViaTargetObjectsLink/obj.f90
  30. 13 0
      Tests/FortranModules/ModulesViaTargetObjectsLink/use.f90
  31. 4 0
      Tests/FortranModules/ModulesViaTargetObjectsSource/CMakeLists.txt
  32. 11 0
      Tests/FortranModules/ModulesViaTargetObjectsSource/obj.f90
  33. 13 0
      Tests/FortranModules/ModulesViaTargetObjectsSource/use.f90
  34. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoBMIInstall-private.json
  35. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoBMIInstall-public.json
  36. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoExport-private.json
  37. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoExport-public.json
  38. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoFileSet-private.json
  39. 1 0
      Tests/RunCMake/CXXModules/expect/NinjaDependInfoFileSet-public.json

+ 62 - 28
Source/cmCommonTargetGenerator.cxx

@@ -163,43 +163,77 @@ std::string cmCommonTargetGenerator::GetIncludes(std::string const& l,
   return i->second;
 }
 
-std::vector<std::string> cmCommonTargetGenerator::GetLinkedTargetDirectories(
+cmCommonTargetGenerator::LinkedTargetDirs
+cmCommonTargetGenerator::GetLinkedTargetDirectories(
   const std::string& lang, const std::string& config) const
 {
-  std::vector<std::string> dirs;
-  std::set<cmGeneratorTarget const*> emitted;
+  LinkedTargetDirs dirs;
+  std::set<cmGeneratorTarget const*> forward_emitted;
+  std::set<cmGeneratorTarget const*> direct_emitted;
   cmGlobalCommonGenerator* const gg = this->GlobalCommonGenerator;
+
+  enum class Forwarding
+  {
+    Yes,
+    No
+  };
+
   if (cmComputeLinkInformation* cli =
         this->GeneratorTarget->GetLinkInformation(config)) {
-    auto addLinkedTarget = [this, &lang, &config, &dirs, &emitted,
-                            gg](cmGeneratorTarget const* linkee) {
-      if (linkee &&
-          !linkee->IsImported()
-          // Skip targets that build after this one in a static lib cycle.
-          && gg->TargetOrderIndexLess(linkee, this->GeneratorTarget)
-          // We can ignore the INTERFACE_LIBRARY items because
-          // Target->GetLinkInformation already processed their
-          // link interface and they don't have any output themselves.
-          && (linkee->GetType() != cmStateEnums::INTERFACE_LIBRARY
-              // Synthesized targets may have relevant rules.
-              || linkee->IsSynthetic()) &&
-          ((lang == "CXX"_s && linkee->HaveCxx20ModuleSources()) ||
-           (lang == "Fortran"_s && linkee->HaveFortranSources(config))) &&
-          emitted.insert(linkee).second) {
-        cmLocalGenerator* lg = linkee->GetLocalGenerator();
-        std::string di = cmStrCat(lg->GetCurrentBinaryDirectory(), '/',
-                                  lg->GetTargetDirectory(linkee));
-        if (lg->GetGlobalGenerator()->IsMultiConfig()) {
-          di = cmStrCat(di, '/', config);
+    auto addLinkedTarget =
+      [this, &lang, &config, &dirs, &direct_emitted, &forward_emitted,
+       gg](cmGeneratorTarget const* linkee, Forwarding forward) {
+        if (linkee &&
+            !linkee->IsImported()
+            // Skip targets that build after this one in a static lib cycle.
+            && gg->TargetOrderIndexLess(linkee, this->GeneratorTarget)
+            // We can ignore the INTERFACE_LIBRARY items because
+            // Target->GetLinkInformation already processed their
+            // link interface and they don't have any output themselves.
+            && (linkee->GetType() != cmStateEnums::INTERFACE_LIBRARY
+                // Synthesized targets may have relevant rules.
+                || linkee->IsSynthetic()) &&
+            ((lang == "CXX"_s && linkee->HaveCxx20ModuleSources()) ||
+             (lang == "Fortran"_s && linkee->HaveFortranSources(config)))) {
+          cmLocalGenerator* lg = linkee->GetLocalGenerator();
+          std::string di = cmStrCat(lg->GetCurrentBinaryDirectory(), '/',
+                                    lg->GetTargetDirectory(linkee));
+          if (lg->GetGlobalGenerator()->IsMultiConfig()) {
+            di = cmStrCat(di, '/', config);
+          }
+          if (forward == Forwarding::Yes &&
+              forward_emitted.insert(linkee).second) {
+            dirs.Forward.push_back(di);
+          }
+          if (direct_emitted.insert(linkee).second) {
+            dirs.Direct.emplace_back(di);
+          }
         }
-        dirs.push_back(std::move(di));
-      }
-    };
+      };
     for (auto const& item : cli->GetItems()) {
-      addLinkedTarget(item.Target);
+      if (item.Target) {
+        addLinkedTarget(item.Target, Forwarding::No);
+      } else if (item.ObjectSource && lang == "Fortran"_s
+                 /* Object source files do not have a language associated with
+                    them. */
+                 /* && item.ObjectSource->GetLanguage() == "Fortran"_s*/) {
+        // Fortran modules provided by `$<TARGET_OBJECTS>` as linked items
+        // should be collated for use in this target.
+        addLinkedTarget(this->LocalCommonGenerator->FindGeneratorTargetToUse(
+                          item.ObjectSource->GetObjectLibrary()),
+                        Forwarding::Yes);
+      }
     }
     for (cmGeneratorTarget const* target : cli->GetExternalObjectTargets()) {
-      addLinkedTarget(target);
+      addLinkedTarget(target, Forwarding::No);
+    }
+    if (lang == "Fortran"_s) {
+      // Fortran modules provided by `$<TARGET_OBJECTS>` as sources should be
+      // collated for use in this target.
+      for (cmGeneratorTarget const* target :
+           this->GeneratorTarget->GetSourceObjectLibraries(config)) {
+        addLinkedTarget(target, Forwarding::Yes);
+      }
     }
   }
   return dirs;

+ 9 - 2
Source/cmCommonTargetGenerator.h

@@ -74,8 +74,15 @@ protected:
 
   std::string GetCompilerLauncher(std::string const& lang,
                                   std::string const& config);
-  std::vector<std::string> GetLinkedTargetDirectories(
-    const std::string& lang, const std::string& config) const;
+
+  struct LinkedTargetDirs
+  {
+    std::vector<std::string> Direct;
+    std::vector<std::string> Forward;
+  };
+
+  LinkedTargetDirs GetLinkedTargetDirectories(const std::string& lang,
+                                              const std::string& config) const;
   std::string ComputeTargetCompilePDB(const std::string& config) const;
 
   std::string GetLinkerLauncher(const std::string& config);

+ 1 - 0
Source/cmComputeLinkDepends.cxx

@@ -681,6 +681,7 @@ void cmComputeLinkDepends::AddLinkObject(cmLinkItem const& item)
   LinkEntry& entry = this->EntryList[index];
   entry.Item = BT<std::string>(item.AsStr(), item.Backtrace);
   entry.Kind = LinkEntry::Object;
+  entry.ObjectSource = item.ObjectSource;
 
   // Record explicitly linked object files separately.
   this->ObjectEntries.emplace_back(index);

+ 4 - 0
Source/cmComputeLinkDepends.h

@@ -22,6 +22,7 @@
 class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmMakefile;
+class cmSourceFile;
 class cmake;
 
 /** \class cmComputeLinkDepends
@@ -63,6 +64,9 @@ public:
 
     BT<std::string> Item;
     cmGeneratorTarget const* Target = nullptr;
+    // The source file representing the external object (used when linking
+    // `$<TARGET_OBJECTS>`)
+    cmSourceFile const* ObjectSource = nullptr;
     EntryKind Kind = Library;
     // The following member is for the management of items specified
     // through genex $<LINK_LIBRARY:...>

+ 13 - 13
Source/cmComputeLinkInformation.cxx

@@ -1176,7 +1176,7 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
         : cmStateEnums::RuntimeBinaryArtifact;
       std::string exe = tgt->GetFullPath(config, artifact, true);
       this->Items.emplace_back(
-        BT<std::string>(exe, item.Backtrace), ItemIsPath::Yes, tgt,
+        BT<std::string>(exe, item.Backtrace), ItemIsPath::Yes, tgt, nullptr,
         this->FindLibraryFeature(entry.Feature == DEFAULT
                                    ? "__CMAKE_LINK_EXECUTABLE"
                                    : entry.Feature));
@@ -1197,7 +1197,7 @@ void cmComputeLinkInformation::AddItem(LinkEntry const& entry)
     } else if (this->GlobalGenerator->IsXcode() &&
                !tgt->GetImportedXcFrameworkPath(config).empty()) {
       this->Items.emplace_back(
-        tgt->GetImportedXcFrameworkPath(config), ItemIsPath::Yes, tgt,
+        tgt->GetImportedXcFrameworkPath(config), ItemIsPath::Yes, tgt, nullptr,
         this->FindLibraryFeature(entry.Feature == DEFAULT
                                    ? "__CMAKE_LINK_XCFRAMEWORK"
                                    : entry.Feature));
@@ -1679,15 +1679,15 @@ void cmComputeLinkInformation::AddTargetItem(LinkEntry const& entry)
       if (isImportedFrameworkFolderOnApple) {
         if (entry.Feature == DEFAULT) {
           this->AddLibraryFeature("FRAMEWORK");
-          this->Items.emplace_back(item, ItemIsPath::Yes, target,
+          this->Items.emplace_back(item, ItemIsPath::Yes, target, nullptr,
                                    this->FindLibraryFeature("FRAMEWORK"));
         } else {
-          this->Items.emplace_back(item, ItemIsPath::Yes, target,
+          this->Items.emplace_back(item, ItemIsPath::Yes, target, nullptr,
                                    this->FindLibraryFeature(entry.Feature));
         }
       } else {
         this->Items.emplace_back(
-          item, ItemIsPath::Yes, target,
+          item, ItemIsPath::Yes, target, nullptr,
           this->FindLibraryFeature(entry.Feature == DEFAULT
                                      ? "__CMAKE_LINK_FRAMEWORK"
                                      : entry.Feature));
@@ -1695,17 +1695,17 @@ void cmComputeLinkInformation::AddTargetItem(LinkEntry const& entry)
     } else {
       if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s)) {
         this->Items.emplace_back(fwDescriptor->GetLinkName(), ItemIsPath::Yes,
-                                 target,
+                                 target, nullptr,
                                  this->FindLibraryFeature(entry.Feature));
       } else if (entry.Feature == DEFAULT &&
                  isImportedFrameworkFolderOnApple) {
         this->AddLibraryFeature("FRAMEWORK");
         this->Items.emplace_back(fwDescriptor->GetLinkName(), ItemIsPath::Yes,
-                                 target,
+                                 target, nullptr,
                                  this->FindLibraryFeature("FRAMEWORK"));
       } else {
         this->Items.emplace_back(
-          item, ItemIsPath::Yes, target,
+          item, ItemIsPath::Yes, target, nullptr,
           this->FindLibraryFeature(entry.Feature == DEFAULT
                                      ? "__CMAKE_LINK_LIBRARY"
                                      : entry.Feature));
@@ -1714,7 +1714,7 @@ void cmComputeLinkInformation::AddTargetItem(LinkEntry const& entry)
   } else {
     // Now add the full path to the library.
     this->Items.emplace_back(
-      item, ItemIsPath::Yes, target,
+      item, ItemIsPath::Yes, target, nullptr,
       this->FindLibraryFeature(
         entry.Feature == DEFAULT ? "__CMAKE_LINK_LIBRARY" : entry.Feature));
   }
@@ -1774,7 +1774,7 @@ void cmComputeLinkInformation::AddFullItem(LinkEntry const& entry)
 
   // Now add the full path to the library.
   this->Items.emplace_back(
-    item, ItemIsPath::Yes, nullptr,
+    item, ItemIsPath::Yes, nullptr, entry.ObjectSource,
     this->FindLibraryFeature(
       entry.Feature == DEFAULT
         ? (entry.Kind == cmComputeLinkDepends::LinkEntry::Object
@@ -2000,13 +2000,13 @@ void cmComputeLinkInformation::AddFrameworkItem(LinkEntry const& entry)
   if (this->GlobalGenerator->IsXcode()) {
     // Add framework path - it will be handled by Xcode after it's added to
     // "Link Binary With Libraries" build phase
-    this->Items.emplace_back(item, ItemIsPath::Yes, nullptr,
+    this->Items.emplace_back(item, ItemIsPath::Yes, nullptr, nullptr,
                              this->FindLibraryFeature(entry.Feature == DEFAULT
                                                         ? "FRAMEWORK"
                                                         : entry.Feature));
   } else {
     this->Items.emplace_back(
-      fwDescriptor->GetLinkName(), ItemIsPath::Yes, nullptr,
+      fwDescriptor->GetLinkName(), ItemIsPath::Yes, nullptr, nullptr,
       this->FindLibraryFeature(entry.Feature == DEFAULT ? "FRAMEWORK"
                                                         : entry.Feature));
   }
@@ -2024,7 +2024,7 @@ void cmComputeLinkInformation::AddXcFrameworkItem(LinkEntry const& entry)
         plist->SelectSuitableLibrary(*this->Makefile, entry.Item.Backtrace)) {
     if (this->GlobalGenerator->IsXcode()) {
       this->Items.emplace_back(
-        entry.Item.Value, ItemIsPath::Yes, nullptr,
+        entry.Item.Value, ItemIsPath::Yes, nullptr, nullptr,
         this->FindLibraryFeature(entry.Feature == DEFAULT
                                    ? "__CMAKE_LINK_XCFRAMEWORK"
                                    : entry.Feature));

+ 6 - 0
Source/cmComputeLinkInformation.h

@@ -22,6 +22,7 @@ class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmMakefile;
 class cmOrderDirectories;
+class cmSourceFile;
 class cmake;
 
 /** \class cmComputeLinkInformation
@@ -51,16 +52,21 @@ public:
   {
     Item(BT<std::string> v, ItemIsPath isPath,
          cmGeneratorTarget const* target = nullptr,
+         cmSourceFile const* objectSource = nullptr,
          FeatureDescriptor const* feature = nullptr)
       : Value(std::move(v))
       , IsPath(isPath)
       , Target(target)
+      , ObjectSource(objectSource)
       , Feature(feature)
     {
     }
     BT<std::string> Value;
     ItemIsPath IsPath = ItemIsPath::No;
     cmGeneratorTarget const* Target = nullptr;
+    // The source file representing the external object (used when linking
+    // `$<TARGET_OBJECTS>`)
+    cmSourceFile const* ObjectSource = nullptr;
 
     bool HasFeature() const { return this->Feature != nullptr; }
     const std::string& GetFeatureName() const

+ 6 - 0
Source/cmDependsFortran.cxx

@@ -258,6 +258,12 @@ bool cmDependsFortran::LocateModules()
     }
     this->MatchRemoteModules(fin, targetDir);
   }
+
+  // TODO: Use `CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES` to handle cases
+  // described in #25425. Note that because Makefiles generators do not
+  // implement relaxed object compilation as described in #15555, the issues
+  // never actually cause build failures; only incremental build incorrectness.
+
   return true;
 }
 

+ 60 - 27
Source/cmGeneratorTarget.cxx

@@ -6966,6 +6966,7 @@ void cmGeneratorTarget::ExpandLinkItems(
             cmSourceFile const* sf =
               mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
             if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
+              item.ObjectSource = sf;
               iface.Objects.emplace_back(std::move(item));
               continue;
             }
@@ -8112,35 +8113,38 @@ void cmGeneratorTarget::GetLanguages(std::set<std::string>& languages,
     }
   }
 
-  std::vector<cmGeneratorTarget*> objectLibraries;
-  std::vector<cmSourceFile const*> externalObjects;
+  std::set<cmGeneratorTarget const*> objectLibraries;
   if (!this->GlobalGenerator->GetConfigureDoneCMP0026()) {
     std::vector<cmGeneratorTarget*> objectTargets;
     this->GetObjectLibrariesCMP0026(objectTargets);
-    objectLibraries.reserve(objectTargets.size());
     for (cmGeneratorTarget* gt : objectTargets) {
-      objectLibraries.push_back(gt);
+      objectLibraries.insert(gt);
     }
   } else {
-    this->GetExternalObjects(externalObjects, config);
-    for (cmSourceFile const* extObj : externalObjects) {
-      std::string objLib = extObj->GetObjectLibrary();
-      if (cmGeneratorTarget* tgt =
-            this->LocalGenerator->FindGeneratorTargetToUse(objLib)) {
-        auto const objLibIt =
-          std::find_if(objectLibraries.cbegin(), objectLibraries.cend(),
-                       [tgt](cmGeneratorTarget* t) { return t == tgt; });
-        if (objectLibraries.cend() == objLibIt) {
-          objectLibraries.push_back(tgt);
-        }
-      }
-    }
+    objectLibraries = this->GetSourceObjectLibraries(config);
   }
-  for (cmGeneratorTarget* objLib : objectLibraries) {
+  for (cmGeneratorTarget const* objLib : objectLibraries) {
     objLib->GetLanguages(languages, config);
   }
 }
 
+std::set<cmGeneratorTarget const*> cmGeneratorTarget::GetSourceObjectLibraries(
+  std::string const& config) const
+{
+  std::set<cmGeneratorTarget const*> objectLibraries;
+  std::vector<cmSourceFile const*> externalObjects;
+  this->GetExternalObjects(externalObjects, config);
+  for (cmSourceFile const* extObj : externalObjects) {
+    std::string objLib = extObj->GetObjectLibrary();
+    if (cmGeneratorTarget* tgt =
+          this->LocalGenerator->FindGeneratorTargetToUse(objLib)) {
+      objectLibraries.insert(tgt);
+    }
+  }
+
+  return objectLibraries;
+}
+
 bool cmGeneratorTarget::IsLanguageUsed(std::string const& language,
                                        std::string const& config) const
 {
@@ -8554,6 +8558,7 @@ void cmGeneratorTarget::ComputeLinkImplementationLibraries(
           cmSourceFile const* sf =
             mf->GetSource(maybeObj, cmSourceFileLocationKind::Known);
           if (sf && sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
+            item.ObjectSource = sf;
             impl.Objects.emplace_back(std::move(item));
             continue;
           }
@@ -9155,19 +9160,47 @@ std::string cmGeneratorTarget::GetImportedXcFrameworkPath(
 bool cmGeneratorTarget::HaveFortranSources(std::string const& config) const
 {
   auto sources = this->GetSourceFiles(config);
-  return std::any_of(sources.begin(), sources.end(),
-                     [](BT<cmSourceFile*> const& sf) -> bool {
-                       return sf.Value->GetLanguage() == "Fortran"_s;
-                     });
+  bool const have_direct = std::any_of(
+    sources.begin(), sources.end(), [](BT<cmSourceFile*> const& sf) -> bool {
+      return sf.Value->GetLanguage() == "Fortran"_s;
+    });
+  bool have_via_target_objects = false;
+  if (!have_direct) {
+    auto const sourceObjectLibraries = this->GetSourceObjectLibraries(config);
+    have_via_target_objects =
+      std::any_of(sourceObjectLibraries.begin(), sourceObjectLibraries.end(),
+                  [&config](cmGeneratorTarget const* tgt) -> bool {
+                    return tgt->HaveFortranSources(config);
+                  });
+  }
+  return have_direct || have_via_target_objects;
 }
 
 bool cmGeneratorTarget::HaveFortranSources() const
 {
-  auto sources = cmGeneratorTarget::GetAllConfigSources();
-  return std::any_of(sources.begin(), sources.end(),
-                     [](AllConfigSource const& sf) -> bool {
-                       return sf.Source->GetLanguage() == "Fortran"_s;
-                     });
+  auto sources = this->GetAllConfigSources();
+  bool const have_direct = std::any_of(
+    sources.begin(), sources.end(), [](AllConfigSource const& sf) -> bool {
+      return sf.Source->GetLanguage() == "Fortran"_s;
+    });
+  bool have_via_target_objects = false;
+  if (!have_direct) {
+    std::vector<std::string> configs =
+      this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+    for (auto const& config : configs) {
+      auto const sourceObjectLibraries =
+        this->GetSourceObjectLibraries(config);
+      have_via_target_objects =
+        std::any_of(sourceObjectLibraries.begin(), sourceObjectLibraries.end(),
+                    [&config](cmGeneratorTarget const* tgt) -> bool {
+                      return tgt->HaveFortranSources(config);
+                    });
+      if (have_via_target_objects) {
+        break;
+      }
+    }
+  }
+  return have_direct || have_via_target_objects;
 }
 
 bool cmGeneratorTarget::HaveCxx20ModuleSources(std::string* errorMessage) const

+ 5 - 0
Source/cmGeneratorTarget.h

@@ -466,6 +466,11 @@ public:
   bool IsLanguageUsed(std::string const& language,
                       std::string const& config) const;
 
+  // Get the set of targets directly referenced via `TARGET_OBJECTS` in the
+  // source list for a configuration.
+  std::set<cmGeneratorTarget const*> GetSourceObjectLibraries(
+    std::string const& config) const;
+
   bool IsCSharpOnly() const;
 
   bool IsDotNetSdkTarget() const;

+ 58 - 1
Source/cmGlobalNinjaGenerator.cxx

@@ -2542,6 +2542,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
   std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
   std::string const& module_dir,
   std::vector<std::string> const& linked_target_dirs,
+  std::vector<std::string> const& forward_modules_from_target_dirs,
   std::string const& arg_lang, std::string const& arg_modmapfmt,
   cmCxxModuleExportInfo const& export_info)
 {
@@ -2819,6 +2820,51 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
   // use by dependents that reference this target in linked-target-dirs.
   std::string const target_mods_file = cmStrCat(
     cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
+
+  // Populate the module map with those provided by linked targets first.
+  for (std::string const& forward_modules_from_target_dir :
+       forward_modules_from_target_dirs) {
+    std::string const fmftn =
+      cmStrCat(forward_modules_from_target_dir, '/', arg_lang, "Modules.json");
+    Json::Value fmft;
+    cmsys::ifstream fmftf(fmftn.c_str(), std::ios::in | std::ios::binary);
+    if (!fmftf) {
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to open ",
+                                    fmftn, " for module information"));
+      return false;
+    }
+    Json::Reader reader;
+    if (!reader.parse(fmftf, fmft, false)) {
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
+                                    forward_modules_from_target_dir,
+                                    reader.getFormattedErrorMessages()));
+      return false;
+    }
+    if (!fmft.isObject()) {
+      continue;
+    }
+
+    auto forward_info = [](Json::Value& target, Json::Value const& source) {
+      if (!source.isObject()) {
+        return;
+      }
+
+      for (auto i = source.begin(); i != source.end(); ++i) {
+        std::string const key = i.key().asString();
+        if (target.isMember(key)) {
+          continue;
+        }
+        target[key] = *i;
+      }
+    };
+
+    // Forward info from forwarding targets into our collation.
+    Json::Value& tmi_target_modules = target_module_info["modules"];
+    forward_info(tmi_target_modules, fmft["modules"]);
+    forward_info(target_references, fmft["references"]);
+    forward_info(target_usages, fmft["usages"]);
+  }
+
   cmGeneratedFileStream tmf(target_mods_file);
   tmf << target_module_info;
 
@@ -2906,6 +2952,16 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
       linked_target_dirs.push_back(tdi_linked_target_dir.asString());
     }
   }
+  std::vector<std::string> forward_modules_from_target_dirs;
+  Json::Value const& tdi_forward_modules_from_target_dirs =
+    tdi["forward-modules-from-target-dirs"];
+  if (tdi_forward_modules_from_target_dirs.isArray()) {
+    for (auto const& tdi_forward_modules_from_target_dir :
+         tdi_forward_modules_from_target_dirs) {
+      forward_modules_from_target_dirs.push_back(
+        tdi_forward_modules_from_target_dir.asString());
+    }
+  }
   std::string const compilerId = tdi["compiler-id"].asString();
   std::string const simulateId = tdi["compiler-simulate-id"].asString();
   std::string const compilerFrontendVariant =
@@ -2929,7 +2985,8 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
 #  endif
   return gg.WriteDyndepFile(dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld,
                             arg_dd, arg_ddis, module_dir, linked_target_dirs,
-                            arg_lang, arg_modmapfmt, *export_info)
+                            forward_modules_from_target_dirs, arg_lang,
+                            arg_modmapfmt, *export_info)
     ? 0
     : 1;
 }

+ 1 - 0
Source/cmGlobalNinjaGenerator.h

@@ -430,6 +430,7 @@ public:
     std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
     std::string const& module_dir,
     std::vector<std::string> const& linked_target_dirs,
+    std::vector<std::string> const& forward_modules_from_target_dirs,
     std::string const& arg_lang, std::string const& arg_modmapfmt,
     cmCxxModuleExportInfo const& export_info);
 

+ 4 - 0
Source/cmLinkItem.h

@@ -17,6 +17,7 @@
 #include "cmTargetLinkLibraryType.h"
 
 class cmGeneratorTarget;
+class cmSourceFile;
 
 // Basic information about each link item.
 class cmLinkItem
@@ -29,6 +30,9 @@ public:
   cmLinkItem(cmGeneratorTarget const* t, bool c, cmListFileBacktrace bt);
   std::string const& AsStr() const;
   cmGeneratorTarget const* Target = nullptr;
+  // The source file representing the external object (used when linking
+  // `$<TARGET_OBJECTS>`)
+  cmSourceFile const* ObjectSource = nullptr;
   bool Cross = false;
   cmListFileBacktrace Backtrace;
   friend bool operator<(cmLinkItem const& l, cmLinkItem const& r);

+ 11 - 0
Source/cmLocalUnixMakefileGenerator3.cxx

@@ -1899,6 +1899,11 @@ void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo(
                         : "OFF")
                   << ")\n\n";
 
+  bool requireFortran = false;
+  if (target->HaveFortranSources(this->GetConfigName())) {
+    requireFortran = true;
+  }
+
   auto const& implicitLangs =
     this->GetImplicitDepends(target, cmDependencyScannerKind::CMake);
 
@@ -1908,6 +1913,12 @@ void cmLocalUnixMakefileGenerator3::WriteDependLanguageInfo(
   cmakefileStream << "set(CMAKE_DEPENDS_LANGUAGES\n";
   for (auto const& implicitLang : implicitLangs) {
     cmakefileStream << "  \"" << implicitLang.first << "\"\n";
+    if (requireFortran && implicitLang.first == "Fortran"_s) {
+      requireFortran = false;
+    }
+  }
+  if (requireFortran) {
+    cmakefileStream << "  \"Fortran\"\n";
   }
   cmakefileStream << "  )\n";
 

+ 3 - 0
Source/cmMakefile.cxx

@@ -3637,6 +3637,9 @@ void cmMakefile::AddTargetObject(std::string const& tgtName,
     this->GetOrCreateSource(objFile, true, cmSourceFileLocationKind::Known);
   sf->SetObjectLibrary(tgtName);
   sf->SetProperty("EXTERNAL_OBJECT", "1");
+  // TODO: Compute a language for this object based on the associated source
+  // file that compiles to it. Needs a policy as it likely affects link
+  // language selection if done unconditionally.
 #if !defined(CMAKE_BOOTSTRAP)
   this->SourceGroups[this->ObjectLibrariesSourceGroupIndex].AddGroupFile(
     sf->ResolveFullPath());

+ 13 - 2
Source/cmMakefileTargetGenerator.cxx

@@ -1442,9 +1442,20 @@ void cmMakefileTargetGenerator::WriteTargetDependRules()
        "# Targets to which this target links which contain Fortran sources.\n"
        "set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES\n";
     /* clang-format on */
-    std::vector<std::string> const dirs =
+    auto const dirs =
       this->GetLinkedTargetDirectories("Fortran", this->GetConfigName());
-    for (std::string const& d : dirs) {
+    for (std::string const& d : dirs.Direct) {
+      *this->InfoFileStream << "  \"" << d << "/DependInfo.cmake\"\n";
+    }
+    *this->InfoFileStream << "  )\n";
+
+    /* clang-format off */
+  *this->InfoFileStream
+    << "\n"
+       "# Targets to which this target links which contain Fortran sources.\n"
+       "set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES\n";
+    /* clang-format on */
+    for (std::string const& d : dirs.Forward) {
       *this->InfoFileStream << "  \"" << d << "/DependInfo.cmake\"\n";
     }
     *this->InfoFileStream << "  )\n";

+ 25 - 5
Source/cmNinjaTargetGenerator.cxx

@@ -1175,6 +1175,15 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     }
   }
 
+  // Check if there are Fortran objects which need to participate in forwarding
+  // module requirements.
+  if (this->GeneratorTarget->HaveFortranSources(config) &&
+      !this->Configs[config].ScanningInfo.count("Fortran")) {
+    ScanningFiles files;
+    this->Configs[config].ScanningInfo["Fortran"].emplace_back(files);
+    this->WriteCompileRule("Fortran", config, WithScanning::Yes);
+  }
+
   for (auto const& langScanningFiles : this->Configs[config].ScanningInfo) {
     std::string const& language = langScanningFiles.first;
     std::vector<ScanningFiles> const& scanningFiles = langScanningFiles.second;
@@ -1197,10 +1206,13 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
 
     this->WriteTargetDependInfo(language, config);
 
-    for (std::string const& l :
-         this->GetLinkedTargetDirectories(language, config)) {
-      build.ImplicitDeps.emplace_back(
-        cmStrCat(l, '/', language, "Modules.json"));
+    auto const linked_directories =
+      this->GetLinkedTargetDirectories(language, config);
+    for (std::string const& l : linked_directories.Direct) {
+      build.ImplicitDeps.push_back(cmStrCat(l, '/', language, "Modules.json"));
+    }
+    for (std::string const& l : linked_directories.Forward) {
+      build.ImplicitDeps.push_back(cmStrCat(l, '/', language, "Modules.json"));
     }
 
     this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
@@ -1902,10 +1914,18 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
 
   Json::Value& tdi_linked_target_dirs = tdi["linked-target-dirs"] =
     Json::arrayValue;
-  for (std::string const& l : this->GetLinkedTargetDirectories(lang, config)) {
+  auto const linked_directories =
+    this->GetLinkedTargetDirectories(lang, config);
+  for (std::string const& l : linked_directories.Direct) {
     tdi_linked_target_dirs.append(l);
   }
 
+  Json::Value& tdi_forward_modules_from_target_dirs =
+    tdi["forward-modules-from-target-dirs"] = Json::arrayValue;
+  for (std::string const& l : linked_directories.Forward) {
+    tdi_forward_modules_from_target_dirs.append(l);
+  }
+
   cmDyndepGeneratorCallbacks cb;
   cb.ObjectFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
     return this->GetObjectFilePath(sf, cnf);

+ 6 - 0
Tests/FortranModules/CMakeLists.txt

@@ -137,3 +137,9 @@ add_subdirectory(Issue25252-iface-sources)
 
 add_subdirectory(Issue25365-target-objects)
 add_subdirectory(Issue25365-target-objects-iface)
+
+# Issue#25425
+add_subdirectory(ModulesViaTargetObjectsSource)
+add_subdirectory(ModulesViaSubdirTargetObjectsSource)
+add_subdirectory(ModulesViaTargetObjectsLink)
+add_subdirectory(ModulesViaSubdirTargetObjectsLink)

+ 7 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/CMakeLists.txt

@@ -0,0 +1,7 @@
+add_subdirectory(subdir)
+
+add_library(mvstol_lib dummy.f90)
+target_link_libraries(mvstol_lib PRIVATE "$<TARGET_OBJECTS:mvstol_obj>")
+target_include_directories(mvstol_lib PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/subdir")
+add_library(mvstol_use use.f90)
+target_link_libraries(mvstol_use PRIVATE mvstol_lib)

+ 3 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/dummy.f90

@@ -0,0 +1,3 @@
+pure real function dummy()
+dummy = 4*atan(1.)
+end function

+ 1 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/subdir/CMakeLists.txt

@@ -0,0 +1 @@
+add_library(mvstol_obj STATIC obj.f90)

+ 11 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/subdir/obj.f90

@@ -0,0 +1,11 @@
+module m1
+
+implicit none
+
+contains
+
+pure real function pi()
+pi = 4*atan(1.)
+end function
+
+end module m1

+ 13 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsLink/use.f90

@@ -0,0 +1,13 @@
+module lib
+
+use m1, only : pi
+
+implicit none
+
+contains
+
+pure real function func()
+func = pi()
+end function
+
+end module

+ 6 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/CMakeLists.txt

@@ -0,0 +1,6 @@
+add_subdirectory(subdir)
+
+add_library(mvstos_lib "$<TARGET_OBJECTS:mvstos_obj>")
+target_include_directories(mvstos_lib PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/subdir")
+add_library(mvstos_use use.f90)
+target_link_libraries(mvstos_use PRIVATE mvstos_lib)

+ 1 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/subdir/CMakeLists.txt

@@ -0,0 +1 @@
+add_library(mvstos_obj OBJECT obj.f90)

+ 11 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/subdir/obj.f90

@@ -0,0 +1,11 @@
+module m1
+
+implicit none
+
+contains
+
+pure real function pi()
+pi = 4*atan(1.)
+end function
+
+end module m1

+ 13 - 0
Tests/FortranModules/ModulesViaSubdirTargetObjectsSource/use.f90

@@ -0,0 +1,13 @@
+module lib
+
+use m1, only : pi
+
+implicit none
+
+contains
+
+pure real function func()
+func = pi()
+end function
+
+end module

+ 5 - 0
Tests/FortranModules/ModulesViaTargetObjectsLink/CMakeLists.txt

@@ -0,0 +1,5 @@
+add_library(mvtol_obj STATIC obj.f90)
+add_library(mvtol_lib dummy.f90)
+target_link_libraries(mvtol_lib PRIVATE "$<TARGET_OBJECTS:mvtol_obj>")
+add_library(mvtol_use use.f90)
+target_link_libraries(mvtol_use PRIVATE mvtol_lib)

+ 3 - 0
Tests/FortranModules/ModulesViaTargetObjectsLink/dummy.f90

@@ -0,0 +1,3 @@
+pure real function dummy()
+dummy = 4*atan(1.)
+end function

+ 11 - 0
Tests/FortranModules/ModulesViaTargetObjectsLink/obj.f90

@@ -0,0 +1,11 @@
+module m1
+
+implicit none
+
+contains
+
+pure real function pi()
+pi = 4*atan(1.)
+end function
+
+end module m1

+ 13 - 0
Tests/FortranModules/ModulesViaTargetObjectsLink/use.f90

@@ -0,0 +1,13 @@
+module lib
+
+use m1, only : pi
+
+implicit none
+
+contains
+
+pure real function func()
+func = pi()
+end function
+
+end module

+ 4 - 0
Tests/FortranModules/ModulesViaTargetObjectsSource/CMakeLists.txt

@@ -0,0 +1,4 @@
+add_library(mvtos_obj OBJECT obj.f90)
+add_library(mvtos_lib "$<TARGET_OBJECTS:mvtos_obj>")
+add_library(mvtos_use use.f90)
+target_link_libraries(mvtos_use PRIVATE mvtos_lib)

+ 11 - 0
Tests/FortranModules/ModulesViaTargetObjectsSource/obj.f90

@@ -0,0 +1,11 @@
+module m1
+
+implicit none
+
+contains
+
+pure real function pi()
+pi = 4*atan(1.)
+end function
+
+end module m1

+ 13 - 0
Tests/FortranModules/ModulesViaTargetObjectsSource/use.f90

@@ -0,0 +1,13 @@
+module lib
+
+use m1, only : pi
+
+implicit none
+
+contains
+
+pure real function func()
+func = pi()
+end function
+
+end module

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoBMIInstall-private.json

@@ -45,6 +45,7 @@
   "exports": [],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-bmi-install-private.dir<CONFIG_DIR>"
 }

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoBMIInstall-public.json

@@ -45,6 +45,7 @@
   "exports": [],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-bmi-install-public.dir<CONFIG_DIR>"
 }

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoExport-private.json

@@ -73,6 +73,7 @@
   ],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-exports-private.dir<CONFIG_DIR>"
 }

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoExport-public.json

@@ -73,6 +73,7 @@
   ],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-exports-public.dir<CONFIG_DIR>"
 }

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoFileSet-private.json

@@ -40,6 +40,7 @@
   "exports": [],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-file-sets-private.dir<CONFIG_DIR>"
 }

+ 1 - 0
Tests/RunCMake/CXXModules/expect/NinjaDependInfoFileSet-public.json

@@ -40,6 +40,7 @@
   "exports": [],
   "include-dirs": [],
   "language": "CXX",
+  "forward-modules-from-target-dirs": [],
   "linked-target-dirs": [],
   "module-dir": "<BINARY_DIR>/CMakeFiles/ninja-file-sets-public.dir<CONFIG_DIR>"
 }