diff --git a/Applications/CLI/CMakeLists.txt b/Applications/CLI/CMakeLists.txt
index 3206d3eb380cd8a0b4e027999aaaf487c7916ddc..cfd5b09be80480d394bc2f9c2ead789cfa9cebcb 100644
--- a/Applications/CLI/CMakeLists.txt
+++ b/Applications/CLI/CMakeLists.txt
@@ -62,7 +62,6 @@ target_link_libraries(
             $<$<TARGET_EXISTS:MPI::MPI_CXX>:MPI::MPI_CXX>
             $<$<TARGET_EXISTS:InSituLib>:InSituLib>
             tclap
-            xmlpatch
 )
 
 target_compile_definitions(
diff --git a/Applications/CLI/ogs.cpp b/Applications/CLI/ogs.cpp
index 3a0aef505fe2c3bb3c70132ec802b96d72561f1d..24275613453bcd61de8517ab360b0582ebdc599c 100644
--- a/Applications/CLI/ogs.cpp
+++ b/Applications/CLI/ogs.cpp
@@ -10,11 +10,8 @@
  *
  */
 
-#include <libxml/parser.h>
-#include <libxml/tree.h>
 #include <spdlog/spdlog.h>
 #include <tclap/CmdLine.h>
-#include <xml_patch.h>
 
 #include <chrono>
 #include <sstream>
@@ -192,107 +189,10 @@ int main(int argc, char* argv[])
 
             run_time.start();
 
-            std::string prj_file = project_arg.getValue();
-            auto patch_files = xml_patch_files.getValue();
-            std::string includepath = "";
-            if (BaseLib::getFileExtension(prj_file) == ".xml")
-            {
-                if (!patch_files.empty())
-                {
-                    OGS_FATAL(
-                        "It is not allowed to specify additional patch files "
-                        "if a patch file was already specified as the "
-                        "prj-file.");
-                }
-                auto patch = xmlParseFile(prj_file.c_str());
-                auto node = xmlDocGetRootElement(patch);
-                auto base_file = xmlGetProp(node, (const xmlChar*)"base_file");
-                if (base_file == nullptr)
-                {
-                    OGS_FATAL(
-                        "Error reading base prj file in given patch file {:s}.",
-                        prj_file);
-                }
-                patch_files = {prj_file};
-                std::stringstream ss;
-                ss << base_file;
-                prj_file = BaseLib::joinPaths(BaseLib::extractPath(prj_file),
-                                              ss.str());
-            }
-
-            if (!patch_files.empty())
-            {
-                includepath =
-                    fs::canonical(fs::path(prj_file)).parent_path().string();
-                std::string current_prj_file = prj_file;
-                std::string current_prj_file_base =
-                    BaseLib::extractBaseNameWithoutExtension(current_prj_file);
-                for (const auto& patch_file : patch_files)
-                {
-                    auto patch = xmlParseFile(patch_file.c_str());
-                    if (patch == NULL)
-                    {
-                        OGS_FATAL("Error reading XML diff file {:s}.",
-                                  patch_file);
-                    }
-
-                    auto doc = xmlParseFile(current_prj_file.c_str());
-                    if (doc == NULL)
-                    {
-                        OGS_FATAL("Error reading project file {:s}.",
-                                  current_prj_file);
-                    }
-
-                    auto node = xmlDocGetRootElement(patch);
-                    int rc = 0;
-                    for (node = node ? node->children : NULL; node;
-                         node = node->next)
-                    {
-                        if (node->type != XML_ELEMENT_NODE)
-                            continue;
-
-                        if (!strcmp((char*)node->name, "add"))
-                            rc = xml_patch_add(doc, node);
-                        else if (!strcmp((char*)node->name, "replace"))
-                            rc = xml_patch_replace(doc, node);
-                        else if (!strcmp((char*)node->name, "remove"))
-                            rc = xml_patch_remove(doc, node);
-                        else
-                            continue;
-
-                        if (rc)
-                        {
-                            OGS_FATAL(
-                                "Error while patching prj file {:s} with patch "
-                                "file "
-                                "{:}.",
-                                project_arg.getValue(), patch_file);
-                        }
-                    }
-
-                    current_prj_file_base =
-                        current_prj_file_base + "_" +
-                        BaseLib::extractBaseNameWithoutExtension(patch_file);
-                    current_prj_file = BaseLib::joinPaths(
-                        outdir_arg.getValue(), current_prj_file_base + ".prj");
-
-                    // TODO: make name unique
-                    xmlSaveFile(current_prj_file.c_str(), doc);
-
-                    xmlFreeDoc(doc);
-                    xmlFreeDoc(patch);
-                }
-                xmlCleanupParser();
-                prj_file = current_prj_file;
-            }
-            else
-            {
-                prj_file = project_arg.getValue();
-            }
-
-            auto project_config =
-                BaseLib::makeConfigTree(prj_file, !nonfatal_arg.getValue(),
-                                        "OpenGeoSysProject", includepath);
+            auto project_config = BaseLib::makeConfigTree(
+                project_arg.getValue(), !nonfatal_arg.getValue(),
+                "OpenGeoSysProject", xml_patch_files.getValue(),
+                outdir_arg.getValue());
 
             BaseLib::setProjectDirectory(
                 BaseLib::extractPath(project_arg.getValue()));
diff --git a/BaseLib/CMakeLists.txt b/BaseLib/CMakeLists.txt
index 6eb7030eebe3020e43cc5513c42ac07f91c9a534..77d904ff5ebc578db6db12a629734959e947622b 100644
--- a/BaseLib/CMakeLists.txt
+++ b/BaseLib/CMakeLists.txt
@@ -16,6 +16,7 @@ target_link_libraries(
            $<$<BOOL:${MSVC}>:WinMM> # needed for timeGetTime
            $<$<BOOL:${OGS_BUILD_GUI}>:Qt5::Xml>
            $<$<BOOL:${OGS_BUILD_GUI}>:Qt5::XmlPatterns>
+    PRIVATE xmlpatch
 )
 
 target_compile_definitions(
diff --git a/BaseLib/ConfigTreeUtil.cpp b/BaseLib/ConfigTreeUtil.cpp
index b4596d03868b3c05544d4226d9d43d3a31a6bb0a..cf0ff1de0438a74015cfc86775b21627b3fc70e5 100644
--- a/BaseLib/ConfigTreeUtil.cpp
+++ b/BaseLib/ConfigTreeUtil.cpp
@@ -10,9 +10,15 @@
 
 #include "ConfigTreeUtil.h"
 
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <spdlog/spdlog.h>
+#include <xml_patch.h>
+
 #include <boost/property_tree/xml_parser.hpp>
 #include <regex>
 
+#include "BaseLib/FileTools.h"
 #include "Error.h"
 #include "Logging.h"
 #include "filesystem.h"
@@ -112,15 +118,108 @@ void replace_includes(
 ConfigTreeTopLevel makeConfigTree(const std::string& filepath,
                                   const bool be_ruthless,
                                   const std::string& toplevel_tag,
-                                  std::string includepath)
+                                  std::vector<std::string>
+                                      patch_files,
+                                  std::string outdir)
 {
+    std::string prj_file = filepath;
+    std::string includepath = "";
+    if (BaseLib::getFileExtension(prj_file) == ".xml")
+    {
+        if (!patch_files.empty())
+        {
+            OGS_FATAL(
+                "It is not allowed to specify additional patch files "
+                "if a patch file was already specified as the "
+                "prj-file.");
+        }
+        auto patch = xmlParseFile(prj_file.c_str());
+        auto node = xmlDocGetRootElement(patch);
+        auto base_file = xmlGetProp(node, (const xmlChar*)"base_file");
+        if (base_file == nullptr)
+        {
+            OGS_FATAL("Error reading base prj file in given patch file {:s}.",
+                      prj_file);
+        }
+        patch_files = {prj_file};
+        std::stringstream ss;
+        ss << base_file;
+        prj_file = BaseLib::joinPaths(BaseLib::extractPath(prj_file), ss.str());
+    }
+
+    if (!patch_files.empty())
+    {
+        includepath = fs::canonical(fs::path(prj_file)).parent_path().string();
+        std::string current_prj_file = prj_file;
+        std::string current_prj_file_base =
+            BaseLib::extractBaseNameWithoutExtension(current_prj_file);
+        for (const auto& patch_file : patch_files)
+        {
+            auto patch = xmlParseFile(patch_file.c_str());
+            if (patch == NULL)
+            {
+                OGS_FATAL("Error reading XML diff file {:s}.", patch_file);
+            }
+
+            auto doc = xmlParseFile(current_prj_file.c_str());
+            if (doc == NULL)
+            {
+                OGS_FATAL("Error reading project file {:s}.", current_prj_file);
+            }
+
+            auto node = xmlDocGetRootElement(patch);
+            int rc = 0;
+            for (node = node ? node->children : NULL; node; node = node->next)
+            {
+                if (node->type != XML_ELEMENT_NODE)
+                    continue;
+
+                if (!strcmp((char*)node->name, "add"))
+                    rc = xml_patch_add(doc, node);
+                else if (!strcmp((char*)node->name, "replace"))
+                    rc = xml_patch_replace(doc, node);
+                else if (!strcmp((char*)node->name, "remove"))
+                    rc = xml_patch_remove(doc, node);
+                else
+                    continue;
+
+                if (rc)
+                {
+                    OGS_FATAL(
+                        "Error while patching prj file {:s} with patch "
+                        "file "
+                        "{:}.",
+                        filepath, patch_file);
+                }
+            }
+
+            current_prj_file_base =
+                current_prj_file_base + "_" +
+                BaseLib::extractBaseNameWithoutExtension(patch_file);
+            current_prj_file =
+                BaseLib::joinPaths(outdir, current_prj_file_base + ".prj");
+
+            // TODO: make name unique
+            xmlSaveFile(current_prj_file.c_str(), doc);
+
+            xmlFreeDoc(doc);
+            xmlFreeDoc(patch);
+        }
+        xmlCleanupParser();
+        prj_file = current_prj_file;
+    }
+    else
+    {
+        prj_file = filepath;
+    }
+
     ConfigTree::PTree ptree;
 
     // note: Trimming whitespace and ignoring comments is crucial in order
     //       for our configuration tree implementation to work!
     try
     {
-        read_xml(filepath, ptree,
+        read_xml(prj_file, ptree,
                  boost::property_tree::xml_parser::no_comments |
                      boost::property_tree::xml_parser::trim_whitespace);
 
diff --git a/BaseLib/ConfigTreeUtil.h b/BaseLib/ConfigTreeUtil.h
index 1a9d13c8c6f697c59ef64e71b4dd4eaa3b4cc9ad..484e6038b87df2cab29dd0d338fe3e8c85af0afa 100644
--- a/BaseLib/ConfigTreeUtil.h
+++ b/BaseLib/ConfigTreeUtil.h
@@ -95,6 +95,7 @@ private:
 ConfigTreeTopLevel makeConfigTree(std::string const& filepath,
                                   bool const be_ruthless,
                                   std::string const& toplevel_tag,
-                                  std::string includepath = "");
+                                  std::vector<std::string> patch_files = {},
+                                  std::string outdir = "");
 
 }  // namespace BaseLib