diff --git a/core/base/src/TSystem.cxx b/core/base/src/TSystem.cxx
index bea171a7d7acdca94c05550aed6427918c7eaaf2..fc70a70d122d1c0f7a3cf47f6b29b8f019b52446 100644
--- a/core/base/src/TSystem.cxx
+++ b/core/base/src/TSystem.cxx
@@ -2,7 +2,7 @@
 // Author: Fons Rademakers   15/09/95
 
 /*************************************************************************
- * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
+ * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
  * All rights reserved.                                                  *
  *                                                                       *
  * For the licensing terms see $ROOTSYS/LICENSE.                         *
@@ -3535,9 +3535,35 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt,
    }
    if (gEnv) {
       TString fromConfig = gEnv->GetValue("ACLiC.IncludePaths","");
-      rcling.Append(fromConfig).Append(" \"");
+      rcling.Append(fromConfig);
    }
-   rcling.Append(filename_fullpath).Append("\" \"").Append(linkdef).Append("\"");;
+
+   // Create a modulemap
+   // FIXME: Merge the modulemap generation from cmake and here in rootcling.
+   if (useCxxModules) {
+      // TString moduleMapFileName = file_dirname + "/" + libname + ".modulemap";
+      TString moduleName = libname + "_ACLiC_dict";
+      if (moduleName.BeginsWith("lib"))
+          moduleName = moduleName.Remove(0, 3);
+      TString moduleMapName = moduleName + ".modulemap";
+      TString moduleMapFullPath = build_loc + "/" + moduleMapName;
+      // A modulemap may exist from previous runs, overwrite it.
+      if (verboseLevel > 3 && !AccessPathName(moduleMapFullPath))
+         ::Info("ACLiC", "File %s already exists!", moduleMapFullPath.Data());
+
+      std::ofstream moduleMapFile(moduleMapFullPath, std::ios::out);
+      moduleMapFile << "module \"" << moduleName << "\" {" << std::endl;
+      moduleMapFile << "  header \"" << filename_fullpath << "\"" << std::endl;
+      moduleMapFile << "  export *" << std::endl;
+      moduleMapFile << "  link \"" << libname_ext << "\"" << std::endl;
+      moduleMapFile << "}" << std::endl;
+      moduleMapFile.close();
+      gInterpreter->RegisterPrebuiltModulePath(build_loc.Data(), moduleMapName.Data());
+      rcling.Append(" \"-fmodule-map-file=" + moduleMapFullPath + "\" ");
+   }
+
+   rcling.Append(" \"").Append(filename_fullpath).Append("\" ");
+   rcling.Append("\"").Append(linkdef).Append("\"");
 
    // ======= Run rootcling
    if (withInfo) {
diff --git a/core/dictgen/src/rootcling_impl.cxx b/core/dictgen/src/rootcling_impl.cxx
index 42bd2386737634cd08d75f4cec8ac9d6d47db1e4..1bb533dd5d626e0a3afcedbbf41f8694dc79f53c 100644
--- a/core/dictgen/src/rootcling_impl.cxx
+++ b/core/dictgen/src/rootcling_impl.cxx
@@ -3664,6 +3664,29 @@ public:
          }
       }
    }
+
+   // rootcling pre-includes things such as Rtypes.h. This means that ACLiC can
+   // call rootcling asking it to create a module for a file with no #includes
+   // but relying on things from Rtypes.h such as the ClassDef macro.
+   //
+   // When rootcling starts building a module, it becomes resilient to the
+   // outside environment and pre-included files have no effect. This hook
+   // informs rootcling when a new submodule is being built so that it can
+   // make Core.Rtypes.h visible.
+   virtual void EnteredSubmodule(clang::Module* M,
+                                 clang::SourceLocation ImportLoc,
+                                 bool ForPragma) {
+      assert(M);
+      using namespace clang;
+      if (llvm::StringRef(M->Name).endswith("ACLiC_dict")) {
+         Preprocessor& PP = m_Interpreter->getCI()->getPreprocessor();
+         HeaderSearch& HS = PP.getHeaderSearchInfo();
+         // FIXME: Reduce to Core.Rtypes.h.
+         Module* CoreModule = HS.lookupModule("Core", /*AllowSearch*/false);
+         assert(M && "Must have module Core");
+         PP.makeModuleVisible(CoreModule, ImportLoc);
+      }
+   }
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -3991,7 +4014,6 @@ int RootClingMain(int argc,
    bool selSyntaxOnly = false;
    bool noIncludePaths = false;
    bool cxxmodule = false;
-   bool isAclic = false;
 
    // Collect the diagnostic pragmas linked to the usage of -W
    // Workaround for ROOT-5656
@@ -4137,8 +4159,6 @@ int RootClingMain(int argc,
       }
       ic++;
    }
-   if (liblistPrefix.length())
-      isAclic = true;
 
    // Check if we have a multi dict request but no target library
    if (multiDict && sharedLibraryPathName.empty()) {
@@ -4895,14 +4915,14 @@ int RootClingMain(int argc,
       }
 
       modGen.WriteRegistrationSource(dictStream, fwdDeclnArgsToKeepString, headersClassesMapString, fwdDeclsString,
-                                     extraIncludes, cxxmodule && !isAclic);
+                                     extraIncludes, cxxmodule);
       // If we just want to inline the input header, we don't need
       // to generate any files.
       if (!inlineInputHeader) {
          // Write the module/PCH depending on what mode we are on
          if (modGen.IsPCH()) {
             if (!GenerateAllDict(modGen, CI, currentDirectory)) return 1;
-         } else if (cxxmodule && !isAclic) {
+         } else if (cxxmodule) {
             if (!CheckModuleValid(modGen, resourceDir, interp, linkdefFilename, moduleName.str()))
                return 1;
          }
@@ -5017,7 +5037,6 @@ int RootClingMain(int argc,
    // Manually call end of translation unit because we never call the
    // appropriate deconstructors in the interpreter. This writes out the C++
    // module file that we currently generate.
-   if (!isAclic)
    {
       cling::Interpreter::PushTransactionRAII RAII(&interp);
       CI->getSema().getASTConsumer().HandleTranslationUnit(CI->getSema().getASTContext());
diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h
index 87fd9f09fd9886d6900e39b1e0e5bb5b352c9eb3..92fa634d544ce69b45dbc2eef99d83247441a5f0 100644
--- a/core/meta/inc/TInterpreter.h
+++ b/core/meta/inc/TInterpreter.h
@@ -176,6 +176,8 @@ public:
    virtual Long_t   ProcessLine(const char *line, EErrorCode *error = 0) = 0;
    virtual Long_t   ProcessLineSynch(const char *line, EErrorCode *error = 0) = 0;
    virtual void     PrintIntro() = 0;
+   virtual bool     RegisterPrebuiltModulePath(const std::string& FullPath,
+                                               const std::string& ModuleMapName = "module.modulemap") const = 0;
    virtual void     RegisterModule(const char* /*modulename*/,
                                    const char** /*headers*/,
                                    const char** /*includePaths*/,
diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx
index 18764c28cbf1e7f958a4cea00709046931ad3722..87fc2d2944541271e10264f0b855de457876c407 100644
--- a/core/metacling/src/TCling.cxx
+++ b/core/metacling/src/TCling.cxx
@@ -1044,35 +1044,6 @@ std::string TCling::ToString(const char* type, void* obj)
    return fInterpreter->toString(type, obj);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-///\returns true if the module map was loaded, false on error or if the map was
-///         already loaded.
-static bool RegisterPrebuiltModulePath(clang::Preprocessor& PP,
-                                       const std::string& FullPath) {
-   assert(llvm::sys::path::is_absolute(FullPath));
-   FileManager& FM = PP.getFileManager();
-   // FIXME: In a ROOT session we can add an include path (through .I /inc/path)
-   // We should look for modulemap files there too.
-   const DirectoryEntry *DE = FM.getDirectory(FullPath);
-   if (DE) {
-      HeaderSearch& HS = PP.getHeaderSearchInfo();
-      const FileEntry *FE = HS.lookupModuleMapFile(DE, /*IsFramework*/ false);
-      const auto &ModPaths = HS.getHeaderSearchOpts().PrebuiltModulePaths;
-      bool pathExists = std::find(ModPaths.begin(), ModPaths.end(), FullPath) != ModPaths.end();
-      if (!pathExists)
-         HS.getHeaderSearchOpts().AddPrebuiltModulePath(FullPath);
-      // FIXME: Calling IsLoaded is slow! Replace this with the appropriate
-      // call to the clang::ModuleMap class.
-      if (FE && !gCling->IsLoaded(FE->getName().data())) {
-         assert(!pathExists && "Prebuilt module path was added w/o loading a modulemap!");
-         if (!HS.loadModuleMapFile(FE, /*IsSystem*/ false))
-            return true;
-         Error("TCling::LoadModule", "Could not load modulemap in the current directory");
-      }
-   }
-   return false;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 ///\returns true if the module was loaded.
 static bool LoadModule(const std::string &ModuleName, cling::Interpreter &interp, bool Complain = true)
@@ -1084,10 +1055,9 @@ static bool LoadModule(const std::string &ModuleName, cling::Interpreter &interp
    //
    // Before failing, try loading the modulemap in the current folder and try
    // loading the requested module from it.
-   clang::Preprocessor& PP = interp.getCI()->getPreprocessor();
    std::string currentDir = gSystem->WorkingDirectory();
    assert(!currentDir.empty());
-   RegisterPrebuiltModulePath(PP, currentDir);
+   gCling->RegisterPrebuiltModulePath(currentDir);
    return interp.loadModule(ModuleName, Complain);
 }
 
@@ -1720,6 +1690,45 @@ namespace {
 
 }
 
+////////////////////////////////////////////////////////////////////////////////
+///\returns true if the module map was loaded, false on error or if the map was
+///         already loaded.
+bool TCling::RegisterPrebuiltModulePath(const std::string &FullPath,
+                                        const std::string &ModuleMapName /*= "module.modulemap"*/) const
+{
+   assert(llvm::sys::path::is_absolute(FullPath));
+   Preprocessor &PP = fInterpreter->getCI()->getPreprocessor();
+   FileManager &FM = PP.getFileManager();
+   // FIXME: In a ROOT session we can add an include path (through .I /inc/path)
+   // We should look for modulemap files there too.
+   const DirectoryEntry *DE = FM.getDirectory(FullPath);
+   if (DE) {
+      HeaderSearch &HS = PP.getHeaderSearchInfo();
+      HeaderSearchOptions &HSOpts = HS.getHeaderSearchOpts();
+      const auto &ModPaths = HSOpts.PrebuiltModulePaths;
+      bool pathExists = std::find(ModPaths.begin(), ModPaths.end(), FullPath) != ModPaths.end();
+      if (!pathExists)
+         HSOpts.AddPrebuiltModulePath(FullPath);
+      // We cannot use HS.lookupModuleMapFile(DE, /*IsFramework*/ false);
+      // because its internal call to getFile has CacheFailure set to true.
+      // In our case, modulemaps can appear any time due to ACLiC.
+      // Code copied from HS.lookupModuleMapFile.
+      llvm::SmallString<256> ModuleMapFileName(DE->getName());
+      llvm::sys::path::append(ModuleMapFileName, ModuleMapName);
+      const FileEntry *FE = FM.getFile(ModuleMapFileName, /*openFile*/ false,
+                                       /*CacheFailure*/ false);
+
+      // FIXME: Calling IsLoaded is slow! Replace this with the appropriate
+      // call to the clang::ModuleMap class.
+      if (FE && !this->IsLoaded(FE->getName().data())) {
+         if (!HS.loadModuleMapFile(FE, /*IsSystem*/ false))
+            return true;
+         Error("RegisterPrebuiltModulePath", "Could not load modulemap in %s", ModuleMapFileName.c_str());
+      }
+   }
+   return false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 /// List of dicts that have the PCM information already in the PCH.
 static const std::unordered_set<std::string> gIgnoredPCMNames = {"libCore",
@@ -2026,13 +2035,17 @@ void TCling::RegisterModule(const char* modulename,
       // specifying the relevant include paths we should try loading the
       // modulemap next to the library location.
       clang::Preprocessor &PP = TheSema.getPreprocessor();
-      // Can be nullptr in case of libCore.
-      if (dyLibName)
-         RegisterPrebuiltModulePath(PP, llvm::sys::path::parent_path(dyLibName));
+      std::string ModuleMapName;
+      if (isACLiC)
+         ModuleMapName = ModuleName + ".modulemap";
+      else
+         ModuleMapName = "module.modulemap";
+      RegisterPrebuiltModulePath(llvm::sys::path::parent_path(dyLibName),
+                                 ModuleMapName);
 
       // FIXME: We should only complain for modules which we know to exist. For example, we should not complain about
       // modules such as GenVector32 because it needs to fall back to GenVector.
-      ModuleWasSuccessfullyLoaded = LoadModule(ModuleName, *fInterpreter, /*Complain=*/ !isACLiC);
+      ModuleWasSuccessfullyLoaded = LoadModule(ModuleName, *fInterpreter, /*Complain=*/true);
       if (!ModuleWasSuccessfullyLoaded) {
          // Only report if we found the module in the modulemap.
          clang::HeaderSearch &headerSearch = PP.getHeaderSearchInfo();
diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h
index f5f0984672c803ca65d41c12334705a6a01acf5d..901509b2e566f21ed4b5f0329040cf0f37c0f7e5 100644
--- a/core/metacling/src/TCling.h
+++ b/core/metacling/src/TCling.h
@@ -223,6 +223,8 @@ public: // Public Interface
    Long_t  ProcessLineAsynch(const char* line, EErrorCode* error = 0);
    Long_t  ProcessLineSynch(const char* line, EErrorCode* error = 0);
    void    PrintIntro();
+   bool    RegisterPrebuiltModulePath(const std::string& FullPath,
+                                      const std::string& ModuleMapName = "module.modulemap") const;
    void    RegisterModule(const char* modulename,
                           const char** headers,
                           const char** includePaths,
diff --git a/interpreter/cling/include/cling/Interpreter/InterpreterCallbacks.h b/interpreter/cling/include/cling/Interpreter/InterpreterCallbacks.h
index 5100fc7398ab8de3b0d77bbe28955856971c5987..0c1a7f0a9c399f9f85e5cac468aa9364f721e1c6 100644
--- a/interpreter/cling/include/cling/Interpreter/InterpreterCallbacks.h
+++ b/interpreter/cling/include/cling/Interpreter/InterpreterCallbacks.h
@@ -110,6 +110,9 @@ namespace cling {
                                    llvm::StringRef /*SearchPath*/,
                                    llvm::StringRef /*RelativePath*/,
                                    const clang::Module* /*Imported*/) {}
+    virtual void EnteredSubmodule(clang::Module* M,
+                                  clang::SourceLocation ImportLoc,
+                                  bool ForPragma) {}
 
     virtual bool FileNotFound(llvm::StringRef FileName,
                               llvm::SmallVectorImpl<char>& RecoveryPath);
diff --git a/interpreter/cling/lib/Interpreter/CIFactory.cpp b/interpreter/cling/lib/Interpreter/CIFactory.cpp
index 6c5d829c20fa34579eefe51c9b253c39dc293d35..113e742bf8ebb12c4abed4f1928e03b8b91fc585 100644
--- a/interpreter/cling/lib/Interpreter/CIFactory.cpp
+++ b/interpreter/cling/lib/Interpreter/CIFactory.cpp
@@ -1236,6 +1236,13 @@ static void stringifyPreprocSetting(PreprocessorOptions& PPOpts,
                                       PP.getTargetInfo().getTriple());
     }
 
+    for (const auto& Filename : FrontendOpts.ModuleMapFiles) {
+      if (auto* File = FM.getFile(Filename))
+        PP.getHeaderSearchInfo().loadModuleMapFile(File, /*IsSystem*/ false);
+      else
+        CI->getDiagnostics().Report(diag::err_module_map_not_found) << Filename;
+    }
+
     return CI.release(); // Passes over the ownership to the caller.
   }
 
diff --git a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp
index 2106eca25709572394477e887e83c95cda7eb3fd..b4087935cfe16b08e9aa896512ec287ab686adc4 100644
--- a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp
+++ b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp
@@ -49,6 +49,12 @@ namespace cling {
                                       SearchPath, RelativePath, Imported);
     }
 
+    void EnteredSubmodule(clang::Module* M,
+                          clang::SourceLocation ImportLoc,
+                          bool ForPragma) override {
+      m_Callbacks->EnteredSubmodule(M, ImportLoc, ForPragma);
+    }
+
     virtual bool FileNotFound(llvm::StringRef FileName,
                               llvm::SmallVectorImpl<char>& RecoveryPath) {
       if (m_Callbacks)
diff --git a/interpreter/cling/lib/Interpreter/MultiplexInterpreterCallbacks.h b/interpreter/cling/lib/Interpreter/MultiplexInterpreterCallbacks.h
index 7d2bfe0ea3203389c129cb6aa399ad319bfb56f6..61b9180ef9e7ed5d596699864ff252afed854e97 100644
--- a/interpreter/cling/lib/Interpreter/MultiplexInterpreterCallbacks.h
+++ b/interpreter/cling/lib/Interpreter/MultiplexInterpreterCallbacks.h
@@ -41,6 +41,12 @@ namespace cling {
                                Imported);
     }
 
+    void EnteredSubmodule(clang::Module* M, clang::SourceLocation ImportLoc,
+                          bool ForPragma) override {
+      for (auto&& cb : m_Callbacks)
+        cb->EnteredSubmodule(M, ImportLoc, ForPragma);
+    }
+
     bool FileNotFound(llvm::StringRef FileName,
                       llvm::SmallVectorImpl<char>& RecoveryPath) override {
       bool result = false;
diff --git a/interpreter/llvm/src/tools/clang/include/clang/Lex/PPCallbacks.h b/interpreter/llvm/src/tools/clang/include/clang/Lex/PPCallbacks.h
index 81c3bd7d14ec5624bbe31949361e10ce7faf6b63..6f4d95c35169943bb5e41d0508baac873558a42f 100644
--- a/interpreter/llvm/src/tools/clang/include/clang/Lex/PPCallbacks.h
+++ b/interpreter/llvm/src/tools/clang/include/clang/Lex/PPCallbacks.h
@@ -128,6 +128,29 @@ public:
                                   const Module *Imported) {
   }
 
+  /// Callback invoked whenever a submodule was entered.
+  ///
+  /// \param M The submodule we have entered.
+  ///
+  /// \param ImportLoc The location of import directive token.
+  ///
+  /// \param ForPragma If entering from pragma directive.
+  ///
+  virtual void EnteredSubmodule(Module *M, SourceLocation ImportLoc,
+                                bool ForPragma) { }
+
+  /// Callback invoked whenever a submodule was left.
+  ///
+  /// \param M The submodule we have left.
+  ///
+  /// \param ImportLoc The location of import directive token.
+  ///
+  /// \param ForPragma If entering from pragma directive.
+  ///
+  virtual void LeftSubmodule(Module *M, SourceLocation ImportLoc,
+                             bool ForPragma) { }
+
+
   /// \brief Callback invoked whenever there was an explicit module-import
   /// syntax.
   ///
@@ -365,6 +388,18 @@ public:
                                Imported);
   }
 
+  void EnteredSubmodule(Module *M, SourceLocation ImportLoc,
+                        bool ForPragma) override {
+    First->EnteredSubmodule(M, ImportLoc, ForPragma);
+    Second->EnteredSubmodule(M, ImportLoc, ForPragma);
+  }
+
+  void LeftSubmodule(Module *M, SourceLocation ImportLoc,
+                     bool ForPragma) override {
+    First->LeftSubmodule(M, ImportLoc, ForPragma);
+    Second->LeftSubmodule(M, ImportLoc, ForPragma);
+  }
+
   void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
                     const Module *Imported) override {
     First->moduleImport(ImportLoc, Path, Imported);
diff --git a/interpreter/llvm/src/tools/clang/lib/Lex/PPLexerChange.cpp b/interpreter/llvm/src/tools/clang/lib/Lex/PPLexerChange.cpp
index 36d7028da6886ad26039df51cfe955e060337d26..d66060ab1057721aa8e38e546e445119a445806d 100644
--- a/interpreter/llvm/src/tools/clang/lib/Lex/PPLexerChange.cpp
+++ b/interpreter/llvm/src/tools/clang/lib/Lex/PPLexerChange.cpp
@@ -665,6 +665,8 @@ void Preprocessor::EnterSubmodule(Module *M, SourceLocation ImportLoc,
     BuildingSubmoduleStack.push_back(
         BuildingSubmoduleInfo(M, ImportLoc, ForPragma, CurSubmoduleState,
                               PendingModuleMacroNames.size()));
+    if (Callbacks)
+      Callbacks->EnteredSubmodule(M, ImportLoc, ForPragma);
     return;
   }
 
@@ -709,6 +711,9 @@ void Preprocessor::EnterSubmodule(Module *M, SourceLocation ImportLoc,
       BuildingSubmoduleInfo(M, ImportLoc, ForPragma, CurSubmoduleState,
                             PendingModuleMacroNames.size()));
 
+  if (Callbacks)
+    Callbacks->EnteredSubmodule(M, ImportLoc, ForPragma);
+
   // Switch to this submodule as the current submodule.
   CurSubmoduleState = &State;
 
@@ -749,6 +754,10 @@ Module *Preprocessor::LeaveSubmodule(bool ForPragma) {
     // are tracking macro visibility, don't build any, and preserve the list
     // of pending names for the surrounding submodule.
     BuildingSubmoduleStack.pop_back();
+
+    if (Callbacks)
+      Callbacks->LeftSubmodule(LeavingMod, ImportLoc, ForPragma);
+
     makeModuleVisible(LeavingMod, ImportLoc);
     return LeavingMod;
   }
@@ -833,6 +842,9 @@ Module *Preprocessor::LeaveSubmodule(bool ForPragma) {
 
   BuildingSubmoduleStack.pop_back();
 
+  if (Callbacks)
+    Callbacks->LeftSubmodule(LeavingMod, ImportLoc, ForPragma);
+
   // A nested #include makes the included submodule visible.
   makeModuleVisible(LeavingMod, ImportLoc);
   return LeavingMod;