Repository.mk                            |    1 
 codemaker/Executable_netmaker.mk         |   28 
 codemaker/Module_codemaker.mk            |    1 
 codemaker/README.md                      |    3 
 codemaker/source/netmaker/csharpfile.hxx |  105 ++
 codemaker/source/netmaker/netmaker.cxx   |   60 +
 codemaker/source/netmaker/netoptions.cxx |  150 +++
 codemaker/source/netmaker/netoptions.hxx |   33 
 codemaker/source/netmaker/netproduce.cxx | 1182 +++++++++++++++++++++++++++++++
 codemaker/source/netmaker/netproduce.hxx |   69 +
 net_ure/CustomTarget_net_oootypes.mk     |   40 +
 net_ure/CustomTarget_net_uretypes.mk     |   39 +
 net_ure/DotnetLibrary_net_oootypes.mk    |   27 
 net_ure/DotnetLibrary_net_uretypes.mk    |   27 
 net_ure/Module_net_ure.mk                |    4 
 net_ure/README.md                        |    2 
 solenv/gbuild/DotnetLibrary.mk           |   48 -
 17 files changed, 1807 insertions(+), 12 deletions(-)

New commits:
commit e597e712b6e1c199a6241df3c29f35baeef9f457
Author:     RMZeroFour <ritobrot...@gmail.com>
AuthorDate: Wed Jun 12 13:56:05 2024 +0530
Commit:     Hossein <hoss...@libreoffice.org>
CommitDate: Wed Jun 19 09:34:45 2024 +0200

    .NET Bindings: Add netmaker (.NET codemaker)
    
    This commit adds the netmaker executable to the codemaker/ module, to
    generate C# code from UNOIDL specifications.
    
    Also adds some Makefiles in the net_ure/ directory to generate code for
    udkapi and offapi, to build the net_uretypes and net_oootypes assemblies.
    
    Change-Id: Ifb61fe6a0f8f594eaa6ff95b025ba57f247b0d4b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168710
    Tested-by: Jenkins
    Tested-by: Hossein <hoss...@libreoffice.org>
    Reviewed-by: Hossein <hoss...@libreoffice.org>

diff --git a/Repository.mk b/Repository.mk
index 4a505655d209..18ebd4372f31 100644
--- a/Repository.mk
+++ b/Repository.mk
@@ -92,6 +92,7 @@ $(eval $(call 
gb_Helper_register_executables_for_install,SDK,sdk, \
        $(if $(filter MSC,$(COM)),$(if $(filter-out 
AARCH64_TRUE,$(CPUNAME)_$(CROSS_COMPILING)),climaker)) \
        cppumaker \
        javamaker \
+       netmaker \
     $(call gb_CondExeSp2bv,sp2bv) \
        $(if $(filter ODK,$(BUILD_TYPE)),unoapploader) \
        unoidl-read \
diff --git a/codemaker/Executable_netmaker.mk b/codemaker/Executable_netmaker.mk
new file mode 100644
index 000000000000..ce06b76773d7
--- /dev/null
+++ b/codemaker/Executable_netmaker.mk
@@ -0,0 +1,28 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,netmaker))
+
+$(eval $(call gb_Executable_use_libraries,netmaker,\
+    salhelper \
+    sal \
+    unoidl \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,netmaker,\
+    codemaker \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,netmaker,\
+    codemaker/source/netmaker/netmaker \
+    codemaker/source/netmaker/netoptions \
+    codemaker/source/netmaker/netproduce \
+))
+
+# vim:set noet sw=4 ts=4:
diff --git a/codemaker/Module_codemaker.mk b/codemaker/Module_codemaker.mk
index 6571ab2ca586..83624602422c 100644
--- a/codemaker/Module_codemaker.mk
+++ b/codemaker/Module_codemaker.mk
@@ -17,6 +17,7 @@ $(eval $(call gb_Module_add_targets,codemaker,\
     StaticLibrary_codemaker_java \
     Executable_javamaker \
     Executable_cppumaker \
+    Executable_netmaker \
 ))
 endif
 
diff --git a/codemaker/README.md b/codemaker/README.md
index c195112fde69..839ae7d7017b 100644
--- a/codemaker/README.md
+++ b/codemaker/README.md
@@ -5,7 +5,8 @@ Generators for language-binding--specific representations of 
UNOIDL entities:
 - `cppumaker` generates header (`.hdl` and `.hpp`) files for the C++ UNO 
language
   binding
 - `javamaker` generates class files for the JVM language binding
-- the codemaker for .NET is in module `cli_ure`
+- `netmaker` generates C# code files for the .NET language binding
+- `climaker` (the old codemaker for .NET Framework) is in module `cli_ure`
 
 Some of the code is re-used by the skeletonmakers in module `unodevtools`.
 
diff --git a/codemaker/source/netmaker/csharpfile.hxx 
b/codemaker/source/netmaker/csharpfile.hxx
new file mode 100644
index 000000000000..6bdc430552dc
--- /dev/null
+++ b/codemaker/source/netmaker/csharpfile.hxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <string_view>
+
+class CSharpFile
+{
+public:
+    CSharpFile(std::string_view directory, std::string_view typeName)
+        : m_filePath(createFilePath(directory, typeName))
+    {
+    }
+
+public:
+    std::string getPath() const { return m_filePath.string(); }
+
+    void openFile()
+    {
+        std::filesystem::create_directories(m_filePath.parent_path());
+        m_fileStream.open(m_filePath, std::fstream::out | std::fstream::trunc);
+        m_indentLevel = 0;
+    }
+    void closeFile() { m_fileStream.close(); }
+
+    CSharpFile& beginBlock()
+    {
+        beginLine();
+        append("{");
+        endLine();
+        ++m_indentLevel;
+        return *this;
+    }
+    CSharpFile& endBlock()
+    {
+        --m_indentLevel;
+        beginLine();
+        append("}");
+        endLine();
+        return *this;
+    }
+
+    CSharpFile& beginLine()
+    {
+        for (int i = 0; i < m_indentLevel; i++)
+        {
+            m_fileStream << "    ";
+        }
+        return *this;
+    }
+
+    CSharpFile& extraIndent()
+    {
+        m_fileStream << "    ";
+        return *this;
+    }
+
+    CSharpFile& append(std::string_view item)
+    {
+        m_fileStream << item;
+        return *this;
+    }
+
+    CSharpFile& append(std::u16string_view item)
+    {
+        m_fileStream << u2b(item);
+        return *this;
+    }
+
+    CSharpFile& endLine()
+    {
+        m_fileStream << '
';
+        return *this;
+    }
+
+private:
+    static std::filesystem::path createFilePath(std::string_view dir, 
std::string_view type)
+    {
+        std::string subdir(type);
+        for (char& c : subdir)
+            if (c == '.')
+                c = '/';
+
+        std::filesystem::path path(dir);
+        path /= subdir + ".cs";
+        return path;
+    }
+
+private:
+    std::filesystem::path m_filePath;
+    std::ofstream m_fileStream;
+    int m_indentLevel = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/codemaker/source/netmaker/netmaker.cxx 
b/codemaker/source/netmaker/netmaker.cxx
new file mode 100644
index 000000000000..64e148817619
--- /dev/null
+++ b/codemaker/source/netmaker/netmaker.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <iostream>
+
+#include <sal/main.h>
+#include <unoidl/unoidl.hxx>
+
+#include "netoptions.hxx"
+#include "netproduce.hxx"
+
+SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
+{
+    try
+    {
+        NetOptions options;
+        NetProducer producer;
+
+        if (options.initOptions(argc, argv))
+        {
+            producer.initProducer(options);
+            producer.produceAll();
+        }
+    }
+    catch (const ::IllegalArgument& e)
+    {
+        std::cerr << "ERROR: Illegal option " << e.m_message << '
';
+        return EXIT_FAILURE;
+    }
+    catch (const ::CannotDumpException& e)
+    {
+        std::cerr << "ERROR: Could not dump as " << e.getMessage() << '
';
+        return EXIT_FAILURE;
+    }
+    catch (const unoidl::NoSuchFileException& e)
+    {
+        std::cerr << "ERROR: No such file " << e.getUri() << '
';
+        return EXIT_FAILURE;
+    }
+    catch (const unoidl::FileFormatException& e)
+    {
+        std::cerr << "ERROR: Bad format of " << e.getUri() << ", '" << 
e.getDetail() << "'
";
+        return EXIT_FAILURE;
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << "ERROR: " << e.what() << '
';
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/codemaker/source/netmaker/netoptions.cxx 
b/codemaker/source/netmaker/netoptions.cxx
new file mode 100644
index 000000000000..5050bc7fde28
--- /dev/null
+++ b/codemaker/source/netmaker/netoptions.cxx
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <iostream>
+#include <string>
+
+#include "netoptions.hxx"
+
+// the third parameter, bCmdFile, is unimplemented and hence unused
+bool NetOptions::initOptions(int argc, char* argv[], bool)
+{
+    if (argc < 2)
+    {
+        std::cerr << prepareHelp();
+        return false;
+    }
+
+    for (int i = 1; i < argc; i++)
+    {
+        OString argument = argv[i];
+
+        if (argument == "-h"_ostr || argument == "--help"_ostr)
+        {
+            std::cout << prepareHelp();
+            return false;
+        }
+        else if (argument == "-v"_ostr || argument == "--verbose"_ostr)
+        {
+            m_options["--verbose"_ostr] = ""_ostr;
+        }
+        else if (argument == "-n"_ostr || argument == "--dry-run"_ostr)
+        {
+            m_options["--dry-run"_ostr] = ""_ostr;
+            // dry run implies verbose
+            m_options["--verbose"_ostr] = "--dry-run"_ostr;
+        }
+        else if (argument == "-T"_ostr || argument == "--types"_ostr)
+        {
+            if (i + 1 < argc)
+            {
+                if (m_options.count("--types"_ostr) == 0)
+                {
+                    m_options["--types"_ostr] = argv[++i];
+                }
+                else
+                {
+                    m_options["--types"_ostr] += ";"_ostr + argv[++i];
+                }
+            }
+            else
+            {
+                throw IllegalArgument("-T/--types must be followed by type 
name or wildcard"_ostr);
+            }
+        }
+        else if (argument == "-X"_ostr || argument == "--extra-types"_ostr)
+        {
+            if (i + 1 < argc)
+            {
+                m_extra_input_files.emplace_back(argv[++i]);
+            }
+            else
+            {
+                throw IllegalArgument("-X/--extra-types must be followed by 
.rdb file"_ostr);
+            }
+        }
+        else if (argument == "-O"_ostr || argument == "--output-dir"_ostr)
+        {
+            if (i + 1 < argc)
+            {
+                m_options["--output-dir"_ostr] = argv[++i];
+            }
+            else
+            {
+                throw IllegalArgument("-O/--output-dir must be followed by 
directory"_ostr);
+            }
+        }
+        else
+        {
+            m_inputFiles.emplace_back(argument);
+        }
+    }
+
+    if (m_inputFiles.empty())
+    {
+        throw IllegalArgument("at least one .rdb file must be provided"_ostr);
+    }
+
+    if (m_options.count("--output-dir"_ostr) == 0)
+    {
+        throw IllegalArgument("-O/--output-dir must be provided"_ostr);
+    }
+
+    return true;
+}
+
+OString NetOptions::prepareHelp()
+{
+    return prepareVersion() + R"(
+
+About:
+    netmaker is a tool for generating C# files from a type library generated 
by the UNOIDL compiler unoidl-write.
+    The generated code files require a reference to the net_basetypes.dll 
assembly to build.
+
+Usage:
+    netmaker [-v|--verbose] [-n|--dry-run]
+        [-T|--types <type name or wildcard>]
+        [-X|--extra-types <.rdb file>]
+        -O|--output-dir <output directory>
+        <rdb file(s)>
+
+Options:
+    -h, --help
+    Display this help message.
+
+    -v, --verbose
+    Log the name of every file created and type generated to stdout.
+
+    -n, --dry-run
+    Do not write generated files to disk. Implies --verbose.
+
+    -T, --types <type name or wildcard>
+    Specify a type name or a wildcard pattern to generate code for. This 
option can be specified multiple times. If not specified, all types in the 
given .rdb files are generated.
+
+    -X, --extra-types <.rdb file>
+    Use an .rdb file containing types to be taken into account without 
generating output for them. This option can be specified multiple times.
+
+    -O, --output-dir <directory>
+    Specify the directory to write generated files to.
+
+Examples:
+    netmaker --verbose -T com.acme.XSomething \
+        -X types.rdb -O acme/ acmetypes.rdb
+
+    netmaker --dry-run -T com.acme.* -X types.rdb \
+        -X offapi.rdb -O acme/ acmetypes.rdb
+
+    netmaker -X types.rdb -O acme/ \
+        acmetypes.rdb moretypes.rdb
+)"_ostr;
+}
+
+OString NetOptions::prepareVersion() const { return m_program + " version 
"_ostr + m_version; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/codemaker/source/netmaker/netoptions.hxx 
b/codemaker/source/netmaker/netoptions.hxx
new file mode 100644
index 000000000000..95ea1b8e71a5
--- /dev/null
+++ b/codemaker/source/netmaker/netoptions.hxx
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+#include <codemaker/options.hxx>
+
+class NetOptions : public Options
+{
+public:
+    NetOptions()
+    {
+        m_program = "netmaker"_ostr;
+        m_version = "0.1.0"_ostr;
+    }
+
+    bool initOptions(int argc, char* argv[], bool bCmdFile = false) override;
+
+    OString prepareHelp() override;
+    OString prepareVersion() const;
+
+private:
+    OString m_version;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/codemaker/source/netmaker/netproduce.cxx 
b/codemaker/source/netmaker/netproduce.cxx
new file mode 100644
index 000000000000..f2ff615a7803
--- /dev/null
+++ b/codemaker/source/netmaker/netproduce.cxx
@@ -0,0 +1,1182 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <iostream>
+#include <vector>
+
+#include <codemaker/codemaker.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/strbuf.hxx>
+#include <unoidl/unoidl.hxx>
+
+#include "netproduce.hxx"
+#include "csharpfile.hxx"
+
+namespace
+{
+const std::unordered_set<OString> s_reservedKeywords{
+    "abstract"_ostr, "as"_ostr,       "base"_ostr,       "bool"_ostr,      
"break"_ostr,
+    "byte"_ostr,     "case"_ostr,     "catch"_ostr,      "char"_ostr,      
"checked"_ostr,
+    "class"_ostr,    "const"_ostr,    "continue"_ostr,   "decimal"_ostr,   
"default"_ostr,
+    "delegate"_ostr, "do"_ostr,       "double"_ostr,     "else"_ostr,      
"enum"_ostr,
+    "event"_ostr,    "explicit"_ostr, "extern"_ostr,     "false"_ostr,     
"finally"_ostr,
+    "fixed"_ostr,    "float"_ostr,    "for"_ostr,        "foreach"_ostr,   
"goto"_ostr,
+    "if"_ostr,       "implicit"_ostr, "in"_ostr,         "int"_ostr,       
"interface"_ostr,
+    "internal"_ostr, "is"_ostr,       "lock"_ostr,       "long"_ostr,      
"namespace"_ostr,
+    "new"_ostr,      "null"_ostr,     "object"_ostr,     "operator"_ostr,  
"out"_ostr,
+    "override"_ostr, "params"_ostr,   "private"_ostr,    "protected"_ostr, 
"public"_ostr,
+    "readonly"_ostr, "ref"_ostr,      "return"_ostr,     "sbyte"_ostr,     
"sealed"_ostr,
+    "short"_ostr,    "sizeof"_ostr,   "stackalloc"_ostr, "static"_ostr,    
"string"_ostr,
+    "struct"_ostr,   "switch"_ostr,   "this"_ostr,       "throw"_ostr,     
"true"_ostr,
+    "try"_ostr,      "typeof"_ostr,   "uint"_ostr,       "ulong"_ostr,     
"unchecked"_ostr,
+    "unsafe"_ostr,   "ushort"_ostr,   "using"_ostr,      "virtual"_ostr,   
"void"_ostr,
+    "volatile"_ostr, "while"_ostr,
+};
+
+const std::unordered_map<OString, OString> s_baseTypes{
+    { "boolean"_ostr, "bool"_ostr },
+    { "char"_ostr, "char"_ostr },
+    { "byte"_ostr, "sbyte"_ostr },
+    { "short"_ostr, "short"_ostr },
+    { "unsigned short"_ostr, "ushort"_ostr },
+    { "long"_ostr, "int"_ostr },
+    { "unsigned long"_ostr, "uint"_ostr },
+    { "hyper"_ostr, "long"_ostr },
+    { "unsigned hyper"_ostr, "ulong"_ostr },
+    { "float"_ostr, "float"_ostr },
+    { "double"_ostr, "double"_ostr },
+    { "string"_ostr, "string"_ostr },
+    { "void"_ostr, "void"_ostr },
+    { "type"_ostr, "System.Type"_ostr },
+    { "any"_ostr, "com.sun.star.uno.Any"_ostr },
+    { "com.sun.star.uno.Exception"_ostr, "com.sun.star.uno.Exception"_ostr },
+    { "com.sun.star.uno.XInterface"_ostr, 
"com.sun.star.uno.IQueryInterface"_ostr },
+};
+
+std::tuple<bool, std::string_view, std::string_view> 
splitName(std::string_view name)
+{
+    size_t split = name.find_last_of('.');
+    if (split != std::string_view::npos)
+        return std::make_tuple(true, name.substr(0, split), name.substr(split 
+ 1));
+    else
+        return std::make_tuple(false, "", name);
+}
+
+OString getBaseUnoName(std::string_view name)
+{
+    size_t start = name.find_first_not_of("[]");
+    if (start == std::string_view::npos)
+        start = 0;
+
+    size_t end = name.find_first_of('<');
+    if (end == std::string_view::npos)
+        end = name.size();
+
+    return OString(name.substr(start, end - start));
+}
+OString getBaseUnoName(std::u16string_view name) { return 
getBaseUnoName(u2b(name)); }
+
+OString getSafeIdentifier(std::string_view name)
+{
+    OString temp(name);
+    return s_reservedKeywords.contains(temp) ? "@"_ostr + temp : temp;
+}
+OString getSafeIdentifier(std::u16string_view name) { return 
getSafeIdentifier(u2b(name)); }
+
+void separatedForeach(const auto& items, auto&& sepFunc, auto&& itemFunc)
+{
+    for (auto it = items.begin(); it != items.end(); ++it)
+    {
+        if (it != items.begin())
+            sepFunc();
+        itemFunc(*it);
+    }
+}
+}
+
+void NetProducer::initProducer(const NetOptions& options)
+{
+    m_outputDir = options.getOption("--output-dir"_ostr);
+    m_dryRun = options.isValid("--dry-run"_ostr);
+    m_verbose = options.isValid("--verbose"_ostr);
+
+    if (options.isValid("--types"_ostr))
+    {
+        const OString& names(options.getOption("--types"_ostr));
+        for (size_t i = 0; i != std::string_view::npos;)
+        {
+            std::string_view name(o3tl::getToken(names, ';', i));
+            if (name == "*")
+                m_startingTypes.insert(""_ostr);
+            else if (name.ends_with(".*"))
+                m_startingTypes.emplace(name.substr(0, name.size() - 2));
+            else
+                m_startingTypes.emplace(name);
+        }
+    }
+    else
+    {
+        m_startingTypes.insert(""_ostr);
+    }
+
+    for (const OString& file : options.getInputFiles())
+        m_manager->loadProvider(convertToFileUrl(file), true);
+    for (const OString& file : options.getExtraInputFiles())
+        m_manager->loadProvider(convertToFileUrl(file), false);
+}
+
+void NetProducer::produceAll()
+{
+    for (const OString& name : m_startingTypes)
+        produceType(name);
+}
+
+void NetProducer::produceType(const OString& name)
+{
+    if (m_typesProduced.contains(name))
+        return;
+
+    m_typesProduced.insert(name);
+
+    if (s_baseTypes.contains(name))
+        return;
+
+    OUString uname(b2u(name));
+
+    rtl::Reference<unoidl::Entity> entity;
+    rtl::Reference<unoidl::MapCursor> cursor;
+
+    if (m_manager->foundAtPrimaryProvider(uname))
+    {
+        switch (m_manager->getSort(uname, &entity, &cursor))
+        {
+            case codemaker::UnoType::Sort::Module:
+                produceModule(name, cursor);
+                break;
+
+            case codemaker::UnoType::Sort::Enum:
+                produceEnum(name, 
dynamic_cast<unoidl::EnumTypeEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::PlainStruct:
+                producePlainStruct(name,
+                                   
dynamic_cast<unoidl::PlainStructTypeEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::PolymorphicStructTemplate:
+                producePolyStruct(
+                    name, 
dynamic_cast<unoidl::PolymorphicStructTypeTemplateEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::Exception:
+                produceException(name, 
dynamic_cast<unoidl::ExceptionTypeEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::Interface:
+                produceInterface(name, 
dynamic_cast<unoidl::InterfaceTypeEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::Typedef:
+                produceTypedef(name, 
dynamic_cast<unoidl::TypedefEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::ConstantGroup:
+                produceConstantGroup(name,
+                                     
dynamic_cast<unoidl::ConstantGroupEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::SingleInterfaceBasedService:
+                produceService(
+                    name, 
dynamic_cast<unoidl::SingleInterfaceBasedServiceEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::InterfaceBasedSingleton:
+                produceSingleton(
+                    name, 
dynamic_cast<unoidl::InterfaceBasedSingletonEntity*>(entity.get()));
+                break;
+
+            case codemaker::UnoType::Sort::AccumulationBasedService:
+            case codemaker::UnoType::Sort::ServiceBasedSingleton:
+                // old-style services and singletons not supported
+                break;
+
+            default:
+                throw CannotDumpException(u"entity '"_ustr + uname + u"' has 
unexpected type"_ustr);
+        }
+    }
+    else
+    {
+        // type from --extra-types
+        switch (m_manager->getSort(uname, &entity, &cursor))
+        {
+            case codemaker::UnoType::Sort::Typedef:
+                produceTypedef(name, 
dynamic_cast<unoidl::TypedefEntity*>(entity.get()));
+                break;
+
+            default:
+                break;
+        }
+    }
+}
+
+void NetProducer::produceModule(std::string_view name,
+                                const rtl::Reference<unoidl::MapCursor>& 
cursor)
+{
+    OUString moduleName;
+    while (cursor->getNext(&moduleName).is())
+    {
+        OString memberName = name.empty() ? u2b(moduleName) : name + "."_ostr 
+ u2b(moduleName);
+        produceType(memberName);
+    }
+}
+
+void NetProducer::produceEnum(std::string_view name,
+                              const rtl::Reference<unoidl::EnumTypeEntity>& 
entity)
+{
+    CSharpFile file(m_outputDir, name);
+
+    if (m_verbose)
+        std::cout << "[enum] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public enum ")
+        .append(getSafeIdentifier(typeName))
+        .endLine()
+        .beginBlock();
+    for (const auto& member : entity->getMembers())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        file.beginLine()
+            .append(getSafeIdentifier(member.name))
+            .append(" = ")
+            .append(OString::number(member.value))
+            .append(",")
+            .endLine();
+    }
+    file.endBlock();
+
+    if (hasNamespace)
+        file.endBlock();
+
+    file.closeFile();
+}
+
+void NetProducer::producePlainStruct(std::string_view name,
+                                     const 
rtl::Reference<unoidl::PlainStructTypeEntity>& entity)
+{
+    // produce referenced types
+    const auto& base = entity->getDirectBase();
+    if (!base.isEmpty())
+        produceType(getBaseUnoName(base));
+    for (const auto& member : entity->getDirectMembers())
+        produceType(getBaseUnoName(member.type));
+
+    CSharpFile file(m_outputDir, name);
+
+    // verbose and dry run checks
+    if (m_verbose)
+        std::cout << "[struct] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    // output namespace block
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    // generate struct and base structs list
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public class ")
+        .append(getSafeIdentifier(typeName));
+    if (!base.isEmpty())
+        file.append(" : ").append(getNetName(base));
+    file.endLine().beginBlock();
+
+    // generate default constructor
+    file.beginLine()
+        .append("public ")
+        .append(getSafeIdentifier(typeName))
+        .append("()")
+        .endLine()
+        .beginBlock()
+        .endBlock();
+    file.endLine(); // extra blank line
+
+    // generate full constructor
+    std::vector<unoidl::PlainStructTypeEntity::Member> baseFields;
+    {
+        OUString baseTypeName = base;
+        while (!baseTypeName.isEmpty())
+        {
+            rtl::Reference<unoidl::Entity> baseEntity;
+            if (m_manager->getSort(baseTypeName, &baseEntity)
+                != codemaker::UnoType::Sort::PlainStruct)
+                throw CannotDumpException("'" + b2u(name) + "' base type '" + 
baseTypeName
+                                          + "' is not a plain struct");
+
+            rtl::Reference<unoidl::PlainStructTypeEntity> ref(
+                
dynamic_cast<unoidl::PlainStructTypeEntity*>(baseEntity.get()));
+            for (const auto& member : ref->getDirectMembers())
+                baseFields.emplace_back(member);
+
+            baseTypeName = ref->getDirectBase();
+        }
+    }
+
+    std::vector<unoidl::PlainStructTypeEntity::Member> 
allFields(entity->getDirectMembers());
+    allFields.insert(allFields.end(), baseFields.begin(), baseFields.end());
+
+    file.beginLine().append("public 
").append(getSafeIdentifier(typeName)).append("(");
+    separatedForeach(
+        allFields, [&file]() { file.append(", "); },
+        [this, &file](const auto& member) {
+            file.append(getNetName(member.type)).append(" 
").append(getSafeIdentifier(member.name));
+        });
+    file.append(")").endLine();
+    file.beginLine().extraIndent().append(": base(");
+    separatedForeach(baseFields, [&file]() { file.append(", "); },
+                     [&file](const auto& member) { 
file.append(getSafeIdentifier(member.name)); });
+    file.append(")").endLine();
+    file.beginBlock();
+    for (const auto& member : entity->getDirectMembers())
+    {
+        file.beginLine()
+            .append("this.")
+            .append(getSafeIdentifier(member.name))
+            .append(" = ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+    file.endBlock();
+    file.endLine(); // extra blank line
+
+    // generate struct fields
+    for (const auto& member : entity->getDirectMembers())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        file.beginLine()
+            .append(getNetName(member.type))
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::producePolyStruct(
+    std::string_view name,
+    const rtl::Reference<unoidl::PolymorphicStructTypeTemplateEntity>& entity)
+{
+    // produce referenced types
+    for (const auto& member : entity->getMembers())
+        if (!member.parameterized)
+            produceType(getBaseUnoName(member.type));
+
+    CSharpFile file(m_outputDir, name);
+
+    // verbose and dry run checks
+    if (m_verbose)
+        std::cout << "[polystruct] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    // output namespace block
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    // generate struct and type parameters list
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public class ")
+        .append(getSafeIdentifier(typeName))
+        .append("<");
+    separatedForeach(entity->getTypeParameters(), [&file]() { file.append(", 
"); },
+                     [&file](const auto& param) { file.append(param); });
+    file.append(">").endLine().beginBlock();
+
+    // generate default constructor
+    file.beginLine()
+        .append("public ")
+        .append(getSafeIdentifier(typeName))
+        .append("()")
+        .endLine()
+        .beginBlock()
+        .endBlock();
+    file.endLine(); // extra blank line
+
+    // generate full constructor
+    file.beginLine().append("public 
").append(getSafeIdentifier(typeName)).append("(");
+    separatedForeach(entity->getMembers(), [&file]() { file.append(", "); },
+                     [this, &file](const auto& member) {
+                         file.append(member.parameterized ? u2b(member.type)
+                                                          : 
getNetName(member.type))
+                             .append(" ")
+                             .append(getSafeIdentifier(member.name));
+                     });
+    file.append(")").endLine();
+    file.beginBlock();
+    for (const auto& member : entity->getMembers())
+    {
+        file.beginLine()
+            .append("this.")
+            .append(getSafeIdentifier(member.name))
+            .append(" = ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+    file.endBlock();
+    file.endLine(); // extra blank line
+
+    // generate struct fields
+    for (const auto& member : entity->getMembers())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        file.beginLine()
+            .append(member.parameterized ? u2b(member.type) : 
getNetName(member.type))
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::produceException(std::string_view name,
+                                   const 
rtl::Reference<unoidl::ExceptionTypeEntity>& entity)
+{
+    // produce referenced types
+    const auto& base = entity->getDirectBase();
+    if (!base.isEmpty())
+        produceType(getBaseUnoName(base));
+    for (const auto& member : entity->getDirectMembers())
+        produceType(getBaseUnoName(member.type));
+
+    CSharpFile file(m_outputDir, name);
+
+    // verbose and dry run checks
+    if (m_verbose)
+        std::cout << "[exception] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    // output namespace block
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    // generate exception and base exceptions list
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public class ")
+        .append(getSafeIdentifier(typeName));
+    if (!base.isEmpty())
+        file.append(" : ").append(getNetName(base));
+    file.endLine().beginBlock();
+
+    // generate default constructor
+    file.beginLine()
+        .append("public ")
+        .append(getSafeIdentifier(typeName))
+        .append("()")
+        .endLine()
+        .beginBlock()
+        .endBlock();
+    file.endLine(); // extra blank line
+
+    // generate full constructor
+    std::vector<unoidl::ExceptionTypeEntity::Member> baseFields;
+    {
+        OUString baseTypeName = base;
+        while (!baseTypeName.isEmpty())
+        {
+            rtl::Reference<unoidl::Entity> baseEntity;
+            if (m_manager->getSort(baseTypeName, &baseEntity)
+                != codemaker::UnoType::Sort::Exception)
+                throw CannotDumpException("'" + b2u(name) + "' base type '" + 
baseTypeName
+                                          + "' is not an exception");
+
+            rtl::Reference<unoidl::ExceptionTypeEntity> ref(
+                dynamic_cast<unoidl::ExceptionTypeEntity*>(baseEntity.get()));
+            for (const auto& member : ref->getDirectMembers())
+                baseFields.emplace_back(member);
+
+            baseTypeName = ref->getDirectBase();
+        }
+    }
+
+    std::vector<unoidl::ExceptionTypeEntity::Member> 
allFields(entity->getDirectMembers());
+    allFields.insert(allFields.end(), baseFields.begin(), baseFields.end());
+
+    file.beginLine().append("public 
").append(getSafeIdentifier(typeName)).append("(");
+    separatedForeach(
+        allFields, [&file]() { file.append(", "); },
+        [this, &file](const auto& member) {
+            file.append(getNetName(member.type)).append(" 
").append(getSafeIdentifier(member.name));
+        });
+    file.append(")").endLine();
+    file.beginLine().extraIndent().append(": base(");
+    separatedForeach(baseFields, [&file]() { file.append(", "); },
+                     [&file](const auto& member) { 
file.append(getSafeIdentifier(member.name)); });
+    file.append(")").endLine();
+    file.beginBlock();
+    for (const auto& member : entity->getDirectMembers())
+    {
+        file.beginLine()
+            .append("this.")
+            .append(getSafeIdentifier(member.name))
+            .append(" = ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+    file.endBlock();
+    file.endLine(); // extra blank line
+
+    // generate exception fields
+    for (const auto& member : entity->getDirectMembers())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        file.beginLine()
+            .append(getNetName(member.type))
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .append(";")
+            .endLine();
+    }
+
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::produceInterface(std::string_view name,
+                                   const 
rtl::Reference<unoidl::InterfaceTypeEntity>& entity)
+{
+    // produce referenced types
+    for (const auto& base : entity->getDirectMandatoryBases())
+        produceType(getBaseUnoName(base.name));
+    for (const auto& member : entity->getDirectAttributes())
+    {
+        produceType(getBaseUnoName(member.type));
+        for (const auto& e : member.getExceptions)
+            produceType(getBaseUnoName(e));
+        for (const auto& e : member.setExceptions)
+            produceType(getBaseUnoName(e));
+    }
+    for (const auto& member : entity->getDirectMethods())
+    {
+        produceType(getBaseUnoName(member.returnType));
+        for (const auto& e : member.exceptions)
+            produceType(getBaseUnoName(e));
+        for (const auto& p : member.parameters)
+            produceType(getBaseUnoName(p.type));
+    }
+
+    CSharpFile file(m_outputDir, name);
+
+    // verbose and dry run checks
+    if (m_verbose)
+        std::cout << "[interface] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    // output namespace block
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    // generate interface and base interfaces list
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public interface ")
+        .append(getSafeIdentifier(typeName));
+    const auto& bases = entity->getDirectMandatoryBases();
+    if (!bases.empty())
+    {
+        file.append(" : ");
+        separatedForeach(bases, [&file]() { file.append(", "); },
+                         [this, &file](const auto& b) { 
file.append(getNetName(b.name)); });
+    }
+    file.endLine().beginBlock();
+
+    // generate interface properties
+    for (const auto& member : entity->getDirectAttributes())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        if (member.bound)
+            file.beginLine().append("[com.sun.star.uno.Bound]").endLine();
+
+        file.beginLine()
+            .append(getNetName(member.type))
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .endLine()
+            .beginBlock();
+
+        if (!member.getExceptions.empty())
+        {
+            file.beginLine().append("[com.sun.star.uno.Raises(");
+            separatedForeach(member.getExceptions, [&file]() { file.append(", 
"); },
+                             [this, &file](const auto& e) {
+                                 
file.append("typeof(").append(getNetName(e)).append(")");
+                             });
+            file.append(")]").endLine();
+        }
+        file.beginLine().append("get;").endLine();
+
+        if (!member.readOnly)
+        {
+            if (!member.setExceptions.empty())
+            {
+                file.beginLine().append("[com.sun.star.uno.Raises(");
+                separatedForeach(member.setExceptions, [&file]() { 
file.append(", "); },
+                                 [this, &file](const auto& e) {
+                                     
file.append("typeof(").append(getNetName(e)).append(")");
+                                 });
+                file.append(")]").endLine();
+            }
+
+            file.beginLine().append("set;").endLine();
+        }
+        file.endBlock();
+    }
+    if (!entity->getDirectAttributes().empty())
+        file.endLine(); // extra blank line
+
+    // generate interface methods
+    for (const auto& member : entity->getDirectMethods())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        if (!member.exceptions.empty())
+        {
+            file.beginLine().append("[com.sun.star.uno.Raises(");
+            separatedForeach(member.exceptions, [&file]() { file.append(", "); 
},
+                             [this, &file](const auto& e) {
+                                 
file.append("typeof(").append(getNetName(e)).append(")");
+                             });
+            file.append(")]").endLine();
+        }
+        file.beginLine()
+            .append(getNetName(member.returnType))
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .append("(");
+        separatedForeach(
+            member.parameters, [&file]() { file.append(", "); },
+            [this, &file](const auto& p) {
+                using Dir = 
unoidl::InterfaceTypeEntity::Method::Parameter::Direction;
+                switch (p.direction)
+                {
+                    case Dir::DIRECTION_IN:
+                        file.append("in ");
+                        break;
+                    case Dir::DIRECTION_OUT:
+                        file.append("out ");
+                        break;
+                    case Dir::DIRECTION_IN_OUT:
+                        file.append("ref ");
+                        break;
+                }
+                file.append(getNetName(p.type)).append(" 
").append(getSafeIdentifier(p.name));
+            });
+        file.append(");").endLine();
+    }
+
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::produceTypedef(std::string_view name,
+                                 const rtl::Reference<unoidl::TypedefEntity>& 
entity)
+{
+    OString type = u2b(entity->getType());
+
+    produceType(getBaseUnoName(type));
+    m_typedefs.emplace(name, type);
+
+    if (m_verbose)
+        std::cout << "[typedef] " << name << " = " << type << '
';
+}
+
+void NetProducer::produceConstantGroup(std::string_view name,
+                                       const 
rtl::Reference<unoidl::ConstantGroupEntity>& entity)
+{
+    CSharpFile file(m_outputDir, name);
+
+    if (m_verbose)
+        std::cout << "[constants] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public static class ")
+        .append(getSafeIdentifier(typeName))
+        .endLine()
+        .beginBlock();
+    for (const auto& member : entity->getMembers())
+    {
+        for (const auto& anno : member.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+        OString type, value;
+        switch (member.value.type)
+        {
+            case unoidl::ConstantValue::TYPE_BOOLEAN:
+                type = "bool"_ostr;
+                value = member.value.booleanValue ? "true"_ostr : "false"_ostr;
+                break;
+            case unoidl::ConstantValue::TYPE_BYTE:
+                type = "sbyte"_ostr;
+                value = OString::number(member.value.byteValue);
+                break;
+            case unoidl::ConstantValue::TYPE_SHORT:
+                type = "short"_ostr;
+                value = OString::number(member.value.shortValue);
+                break;
+            case unoidl::ConstantValue::TYPE_UNSIGNED_SHORT:
+                type = "ushort"_ostr;
+                value = OString::number(member.value.unsignedShortValue);
+                break;
+            case unoidl::ConstantValue::TYPE_LONG:
+                type = "int"_ostr;
+                value = OString::number(member.value.longValue);
+                break;
+            case unoidl::ConstantValue::TYPE_UNSIGNED_LONG:
+                type = "uint"_ostr;
+                value = OString::number(member.value.unsignedLongValue);
+                break;
+            case unoidl::ConstantValue::TYPE_HYPER:
+                type = "long"_ostr;
+                value = OString::number(member.value.hyperValue);
+                break;
+            case unoidl::ConstantValue::TYPE_UNSIGNED_HYPER:
+                type = "ulong"_ostr;
+                value = OString::number(member.value.unsignedHyperValue);
+                break;
+            case unoidl::ConstantValue::TYPE_FLOAT:
+                type = "float"_ostr;
+                value = OString::number(member.value.floatValue);
+                break;
+            case unoidl::ConstantValue::TYPE_DOUBLE:
+                type = "double"_ostr;
+                value = OString::number(member.value.doubleValue);
+                break;
+        }
+        file.beginLine()
+            .append("public const ")
+            .append(type)
+            .append(" ")
+            .append(getSafeIdentifier(member.name))
+            .append(" = ")
+            .append(value)
+            .append(";")
+            .endLine();
+    }
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::produceService(
+    std::string_view name, const 
rtl::Reference<unoidl::SingleInterfaceBasedServiceEntity>& entity)
+{
+    CSharpFile file(m_outputDir, name);
+
+    if (m_verbose)
+        std::cout << "[service] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public static class ")
+        .append(getSafeIdentifier(typeName))
+        .endLine()
+        .beginBlock();
+
+    for (const auto& ctor : entity->getConstructors())
+    {
+        for (const auto& anno : ctor.annotations)
+            if (anno == "deprecated")
+                file.beginLine().append("[System.Obsolete]").endLine();
+
+        std::vector<OUString> exceptions(ctor.exceptions);
+        exceptions.emplace(exceptions.begin(), 
"com.sun.star.uno.DeploymentException");
+
+        file.beginLine().append("[com.sun.star.uno.Raises(");
+        separatedForeach(exceptions, [&file]() { file.append(", "); },
+                         [this, &file](const auto& e) {
+                             
file.append("typeof(").append(getNetName(e)).append(")");
+                         });
+        file.append(")]").endLine();
+
+        if (ctor.defaultConstructor)
+        {
+            const auto& returnType(getNetName(entity->getBase()));
+
+            file.beginLine()
+                .append("public static ")
+                .append(returnType)
+                .append(" create(in com.sun.star.uno.XComponentContext ctx)")
+                .endLine()
+                .beginBlock();
+
+            file.beginLine()
+                .append("try")
+                .endLine()
+                .beginBlock()
+                .beginLine()
+                .append("com.sun.star.lang.XMultiComponentFactory mcf = ")
+                .append("ctx.getServiceManager();")
+                .endLine()
+                .beginLine()
+                .append(returnType)
+                .append(" srv = (")
+                .append(returnType)
+                .append(")mcf.createInstanceWithContext(\"")
+                .append(returnType)
+                .append("\", ctx);")
+                .endLine()
+                .beginLine()
+                .append("return srv;")
+                .endLine()
+                .endBlock();
+
+            for (const auto& e : ctor.exceptions)
+            {
+                file.beginLine()
+                    .append("catch (")
+                    .append(e)
+                    .append(")")
+                    .endLine()
+                    .beginBlock()
+                    .beginLine()
+                    .append("throw;")
+                    .endLine()
+                    .endBlock();
+            }
+
+            file.beginLine()
+                .append("catch")
+                .endLine()
+                .beginBlock()
+                .beginLine()
+                .append(
+                    "throw new com.sun.star.uno.DeploymentException(\"Could 
not create service ")
+                .append(returnType)
+                .append(" from given XComponentContext\", ctx);")
+                .endLine()
+                .endBlock();
+
+            file.endBlock();
+        }
+        else
+        {
+            const auto& returnType(getNetName(entity->getBase()));
+            const auto* restParam = !ctor.parameters.empty() && 
ctor.parameters.front().rest
+                                        ? &ctor.parameters.front()
+                                        : nullptr;
+
+            file.beginLine()
+                .append("public static ")
+                .append(returnType)
+                .append(" ")
+                .append(getSafeIdentifier(ctor.name))
+                .append("(in com.sun.star.uno.XComponentContext ctx");
+            if (!ctor.parameters.empty())
+                file.append(", ");
+            separatedForeach(
+                ctor.parameters, [&file]() { file.append(", "); },
+                [this, &file](const auto& p) {
+                    file.append(getNetName(p.type)).append(" 
").append(getSafeIdentifier(p.name));
+                });
+            file.append(")").endLine().beginBlock();
+
+            file.beginLine()
+                .append("try")
+                .endLine()
+                .beginBlock()
+                .beginLine()
+                .append("com.sun.star.lang.XMultiComponentFactory mcf = ")
+                .append("ctx.getServiceManager();")
+                .endLine()
+                .beginLine()
+                .append(returnType)
+                .append(" srv = (")
+                .append(returnType)
+                .append(")mcf.createInstanceWithArgumentsAndContext(\"")
+                .append(returnType)
+                .append("\", ");
+            if (restParam)
+            {
+                file.append(getSafeIdentifier(ctor.parameters.front().name));
+            }
+            else if (ctor.parameters.empty())
+            {
+                file.append("System.Array.Empty<com.sun.star.uno.Any>()");
+            }
+            else
+            {
+                file.append("new com.sun.star.uno.Any[] { ");
+                separatedForeach(ctor.parameters, [&file]() { file.append(", 
"); },
+                                 [&file](const auto& p) {
+                                     file.append("com.sun.star.uno.Any.with(")
+                                         .append(getSafeIdentifier(p.name))
+                                         .append(")");
+                                 });
+                file.append(" }");
+            }
+            file.append(", ctx);").endLine().beginLine().append("return 
srv;").endLine().endBlock();
+
+            for (const auto& e : ctor.exceptions)
+            {
+                file.beginLine()
+                    .append("catch (")
+                    .append(e)
+                    .append(")")
+                    .endLine()
+                    .beginBlock()
+                    .beginLine()
+                    .append("throw;")
+                    .endLine()
+                    .endBlock();
+            }
+
+            file.beginLine()
+                .append("catch")
+                .endLine()
+                .beginBlock()
+                .beginLine()
+                .append(
+                    "throw new com.sun.star.uno.DeploymentException(\"Could 
not create service ")
+                .append(returnType)
+                .append(" from given XComponentContext\", ctx);")
+                .endLine()
+                .endBlock();
+
+            file.endBlock();
+        }
+    }
+
+    file.endBlock();
+    if (hasNamespace)
+        file.endBlock();
+    file.closeFile();
+}
+
+void NetProducer::produceSingleton(
+    std::string_view name, const 
rtl::Reference<unoidl::InterfaceBasedSingletonEntity>& entity)
+{
+    CSharpFile file(m_outputDir, name);
+
+    if (m_verbose)
+        std::cout << "[singleton] " << name << " -> " << file.getPath() << '
';
+    if (m_dryRun)
+        return;
+
+    file.openFile();
+
+    auto[hasNamespace, namespaceName, typeName] = splitName(name);
+
+    if (hasNamespace)
+        file.beginLine().append("namespace 
").append(namespaceName).endLine().beginBlock();
+
+    file.beginLine()
+        .append("[com.sun.star.uno.UnoGenerated]")
+        .endLine()
+        .beginLine()
+        .append("public static class ")
+        .append(getSafeIdentifier(typeName))
+        .endLine()
+        .beginBlock();
+
+    file.beginLine()
+        
.append("[com.sun.star.uno.Raises(typeof(com.sun.star.uno.DeploymentException))]")
+        .endLine();
+    file.beginLine()
+        .append("public static ")
+        .append(getNetName(entity->getBase()))
+        .append(" get(in com.sun.star.uno.XComponentContext ctx)")
+        .endLine()
+        .beginBlock();
+
+    file.beginLine()
+        .append("com.sun.star.uno.Any sgtn = 
ctx.getValueByName(\"/singletons/")
+        .append(name)
+        .append("\");")
+        .endLine();
+    file.beginLine()
+        .append("if (!sgtn.hasValue())")
+        .endLine()
+        .beginBlock()
+        .beginLine()
+        .append("throw new com.sun.star.uno.DeploymentException(\"Could not 
get singleton ")
+        .append(name)
+        .append(" from given XComponentContext\", ctx);")
+        .endLine()
+        .endBlock();
+    file.beginLine()
+        .append("return (")
+        .append(getNetName(entity->getBase()))
+        .append(")sgtn.Value;")
+        .endLine();
+
+    file.endBlock().endBlock();
+
+    if (hasNamespace)
+        file.endBlock();
+
+    file.closeFile();
+}
+
+OString NetProducer::getNetName(std::string_view name)
+{
+    OString fullName(name);
+    OStringBuffer buffer;
+
+    while (true)
+    {
+        OString baseName = getBaseUnoName(fullName);
+        if (m_typedefs.contains(baseName))
+            fullName = fullName.replaceFirst(baseName, 
m_typedefs.at(baseName));
+        else
+            break;
+    }
+
+    std::string_view fullNameView(fullName);
+
+    // if sequence, count dimensions
+    int dimensions = 0;
+    while (fullNameView.starts_with("[]"))
+    {
+        ++dimensions;
+        fullNameView = fullNameView.substr(2);
+    }
+
+    // if polymorphic, process parameters too
+    if (fullNameView.ends_with('>'))
+    {
+        size_t start = fullNameView.find_first_of('<') + 1;
+        size_t end = fullNameView.size() - 1;
+        buffer.append(fullNameView.substr(0, start));
+        OString params(fullNameView.substr(start, end - start));
+
+        bool first = true;
+        for (start = 0; start != std::string_view::npos;)
+        {
+            std::string_view param(o3tl::getToken(params, ',', start));
+            if (first)
+                first = false;
+            else
+                buffer.append(", ");
+            buffer.append(getNetName(param));
+        }
+        buffer.append(">");
+    }
+    else
+    {
+        // assumes basetypes are not polymorphic for a tiny optimization
+        // if this is changed later, move this part out of the else block
+        fullName = OString(fullNameView);
+        OString baseName = getBaseUnoName(fullName);
+        if (s_baseTypes.contains(baseName))
+            buffer.append(fullName.replaceFirst(baseName, 
s_baseTypes.at(baseName)));
+        else
+            buffer.append(fullName);
+    }
+
+    // if seqeunce, add [] to make array
+    while (dimensions--)
+        buffer.append("[]");
+
+    return buffer.makeStringAndClear();
+}
+OString NetProducer::getNetName(std::u16string_view name) { return 
getNetName(u2b(name)); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/codemaker/source/netmaker/netproduce.hxx 
b/codemaker/source/netmaker/netproduce.hxx
new file mode 100644
index 000000000000..c7c47c4b390c
--- /dev/null
+++ b/codemaker/source/netmaker/netproduce.hxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <unordered_set>
+#include <string_view>
+
+#include <codemaker/typemanager.hxx>
+#include <rtl/string.hxx>
+#include <unoidl/unoidl.hxx>
+
+#include "netoptions.hxx"
+
+class NetProducer
+{
+public:
+    NetProducer()
+        : m_manager(new TypeManager())
+    {
+    }
+
+public:
+    void initProducer(const NetOptions& options);
+    void produceAll();
+
+private:
+    void produceType(const OString& name);
+    void produceModule(std::string_view name, const 
rtl::Reference<unoidl::MapCursor>& cursor);
+    void produceEnum(std::string_view name, const 
rtl::Reference<unoidl::EnumTypeEntity>& entity);
+    void producePlainStruct(std::string_view name,
+                            const 
rtl::Reference<unoidl::PlainStructTypeEntity>& entity);
+    void
+    producePolyStruct(std::string_view name,
+                      const 
rtl::Reference<unoidl::PolymorphicStructTypeTemplateEntity>& entity);
+    void produceException(std::string_view name,
+                          const rtl::Reference<unoidl::ExceptionTypeEntity>& 
entity);
+    void produceInterface(std::string_view name,
+                          const rtl::Reference<unoidl::InterfaceTypeEntity>& 
entity);
+    void produceTypedef(std::string_view name, const 
rtl::Reference<unoidl::TypedefEntity>& entity);
+    void produceConstantGroup(std::string_view name,
+                              const 
rtl::Reference<unoidl::ConstantGroupEntity>& entity);
+    void produceService(std::string_view name,
+                        const 
rtl::Reference<unoidl::SingleInterfaceBasedServiceEntity>& entity);
+    void produceSingleton(std::string_view name,
+                          const 
rtl::Reference<unoidl::InterfaceBasedSingletonEntity>& entity);
+
+    OString getNetName(std::string_view name);
+    OString getNetName(std::u16string_view name);
+
+private:
+    rtl::Reference<TypeManager> m_manager;
+
+    std::unordered_set<OString> m_startingTypes;
+    std::unordered_set<OString> m_typesProduced;
+    std::unordered_map<OString, OString> m_typedefs;
+
+    OString m_outputDir;
+    bool m_verbose;
+    bool m_dryRun;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/net_ure/CustomTarget_net_oootypes.mk 
b/net_ure/CustomTarget_net_oootypes.mk
new file mode 100644
index 000000000000..b8a007c05db0
--- /dev/null
+++ b/net_ure/CustomTarget_net_oootypes.mk
@@ -0,0 +1,40 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_CustomTarget_CustomTarget,net_oootypes))
+
+net_ure_DIR := $(gb_CustomTarget_workdir)/net_ure
+net_oootypes_DIR := $(gb_CustomTarget_workdir)/net_ure/net_oootypes
+
+$(call gb_CustomTarget_get_target,net_oootypes) : 
$(net_ure_DIR)/net_oootypes.done
+
+$(net_ure_DIR)/net_oootypes.done : \
+               $(call gb_UnoApi_get_target,offapi) \
+               $(call gb_UnoApi_get_target,udkapi) \
+               $(call gb_Executable_get_target,netmaker) \
+               $(call gb_Executable_get_runtime_dependencies,netmaker) \
+               | $(net_oootypes_DIR)/.dir
+       $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),NET,4)
+       $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),NET)
+       $(call gb_Helper_abbreviate_dirs, \
+       rm -r $(net_oootypes_DIR) && \
+       $(call gb_Helper_execute,netmaker -v -O $(net_oootypes_DIR) \
+               -X $(call gb_UnoApi_get_target,udkapi) \
+               $(call gb_UnoApi_get_target,offapi) > $@.log 2>&1 || \
+               (echo \
+                       && cat $@.log \
+                       && echo \
+                       && echo "net_oootypes failed to generate. To retry, 
use:" \
+                       && echo "    make CustomTarget_net_oootypes" \
+                       && echo "cd into the net_ure/ directory to run make 
faster" \
+                       && echo \
+                       && false)) && \
+       touch $@)
+       $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),NET)
+
+# vim: set noet sw=4 ts=4:
diff --git a/net_ure/CustomTarget_net_uretypes.mk 
b/net_ure/CustomTarget_net_uretypes.mk
new file mode 100644
index 000000000000..419f71ac4420
--- /dev/null
+++ b/net_ure/CustomTarget_net_uretypes.mk
@@ -0,0 +1,39 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_CustomTarget_CustomTarget,net_uretypes))
+
+net_ure_DIR := $(gb_CustomTarget_workdir)/net_ure
+net_uretypes_DIR := $(gb_CustomTarget_workdir)/net_ure/net_uretypes
+
+$(call gb_CustomTarget_get_target,net_uretypes) : 
$(net_ure_DIR)/net_uretypes.done
+
+$(net_ure_DIR)/net_uretypes.done : \
+               $(call gb_UnoApi_get_target,udkapi) \
+               $(call gb_Executable_get_target,netmaker) \
+               $(call gb_Executable_get_runtime_dependencies,netmaker) \
+               | $(net_uretypes_DIR)/.dir
+       $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),NET,4)
+       $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),NET)
+       $(call gb_Helper_abbreviate_dirs, \
+       rm -r $(net_uretypes_DIR) && \
+       $(call gb_Helper_execute,netmaker -v -O $(net_uretypes_DIR) \
+               $(call gb_UnoApi_get_target,udkapi) > $@.log 2>&1 || \
+               (echo \
+                       && cat $@.log \
+                       && echo \
+                       && echo "net_uretypes failed to generate. To retry, 
use:" \
+                       && echo "    make CustomTarget_net_uretypes" \
+                       && echo "cd into the net_ure/ directory to run make 
faster" \
+                       && echo \
+                       && false)) && \
+       touch $@)
+       $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),NET)
+
+
+# vim: set noet sw=4 ts=4:
diff --git a/net_ure/DotnetLibrary_net_oootypes.mk 
b/net_ure/DotnetLibrary_net_oootypes.mk
new file mode 100644
index 000000000000..f799f5056764
--- /dev/null
+++ b/net_ure/DotnetLibrary_net_oootypes.mk
@@ -0,0 +1,27 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_DotnetLibrary_CsLibrary,net_oootypes))
+
+$(call gb_DotnetLibrary_get_target,net_oootypes) : \
+    $(call gb_CustomTarget_get_target,net_oootypes)
+
+$(eval $(call gb_DotnetLibrary_add_generated_sources,net_oootypes,\
+    $(gb_CustomTarget_workdir)/net_ure/net_oootypes/**/*.cs \
+))
+
+$(eval $(call gb_DotnetLibrary_link_cs_library,net_oootypes,net_uretypes))
+
+$(eval $(call gb_DotnetLibrary_add_properties,net_oootypes,\
+    <AssemblyName>net_oootypes</AssemblyName> \
+    <Version>0.1.0</Version> \
+    <Company>LibreOffice</Company> \
+    <Description>LibreOffice datatypes for the .NET language UNO 
binding.</Description> \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/net_ure/DotnetLibrary_net_uretypes.mk 
b/net_ure/DotnetLibrary_net_uretypes.mk
new file mode 100644
index 000000000000..a5e6968887c2
--- /dev/null
+++ b/net_ure/DotnetLibrary_net_uretypes.mk
@@ -0,0 +1,27 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$(eval $(call gb_DotnetLibrary_CsLibrary,net_uretypes))
+
+$(call gb_DotnetLibrary_get_target,net_uretypes) : \
+    $(call gb_CustomTarget_get_target,net_uretypes)
+
+$(eval $(call gb_DotnetLibrary_add_generated_sources,net_uretypes,\
+    $(gb_CustomTarget_workdir)/net_ure/net_uretypes/**/*.cs \
+))
+
+$(eval $(call gb_DotnetLibrary_link_cs_library,net_uretypes,net_basetypes))
+
+$(eval $(call gb_DotnetLibrary_add_properties,net_uretypes,\
+    <AssemblyName>net_uretypes</AssemblyName> \
+    <Version>0.1.0</Version> \
+    <Company>LibreOffice</Company> \
+    <Description>UNO runtime datatypes for the .NET language UNO 
binding.</Description> \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/net_ure/Module_net_ure.mk b/net_ure/Module_net_ure.mk
index 2d939717e367..6693a5ad58ee 100644
--- a/net_ure/Module_net_ure.mk
+++ b/net_ure/Module_net_ure.mk
@@ -10,7 +10,11 @@ $(eval $(call gb_Module_Module,net_ure))
 
 ifeq ($(ENABLE_DOTNET),TRUE)
 $(eval $(call gb_Module_add_targets,net_ure,\
+       CustomTarget_net_uretypes \
+       CustomTarget_net_oootypes \
        DotnetLibrary_net_basetypes \
+       DotnetLibrary_net_uretypes \
+       DotnetLibrary_net_oootypes \
 ))
 endif
 
diff --git a/net_ure/README.md b/net_ure/README.md
index 4aaae40a4e86..042981eb9113 100644
--- a/net_ure/README.md
+++ b/net_ure/README.md
@@ -2,4 +2,4 @@
 
 Support assemblies and tools for the newer cross-platform .NET UNO binding.
 
-Currently only contains code for the net_basetypes assembly in the 
source/basetypes subdirectory.
\ No newline at end of file
+Currently only contains code for the `net_basetypes` assembly in the 
source/basetypes subdirectory, as well as Makefiles to build the `net_uretypes` 
(for `udkapi`) and `net_oootypes` (for `offapi`) assemblies, using `netmaker` 
from the codemaker/ module
\ No newline at end of file
diff --git a/solenv/gbuild/DotnetLibrary.mk b/solenv/gbuild/DotnetLibrary.mk
index 10a32b159b40..190a0059e724 100644
--- a/solenv/gbuild/DotnetLibrary.mk
+++ b/solenv/gbuild/DotnetLibrary.mk
@@ -33,6 +33,11 @@ $(strip $(subst ",\",$(1)))
 
 endef
 
+define gb_DotnetLibrary__ensure_absolute
+$(if $(filter $(SRCDIR)%,$(1)),$(1),$(SRCDIR)/$(1))
+
+endef
+
 ####### Build and Clean Targets #########
 
 .PHONY : $(call gb_DotnetLibrary_get_clean_target,%)
@@ -58,7 +63,8 @@ $(call gb_DotnetLibrary_get_target,%) :
                dotnet build $$P $(DOTNET_BUILD_FLAGS) \
                        -o $(call gb_DotnetLibrary_get_workdir,$*) \
                        > $@.log 2>&1 || \
-                       (cat $@.log \
+                       (echo \
+                               && cat $@.log \
                                && echo \
                                && echo "A library failed to build. To retry 
the build, use:" \
                                && echo "    make DotnetLibrary_$*" \
@@ -128,41 +134,63 @@ $(call gb_DotnetLibrary_get_target,$(1)) : 
DOTNET_ITEM_ELEMENTS += $(strip $(cal
 endef
 
 # Add one source file to the project file
-# This add it to the project, and makes it a build dependency
+# This adds it to the project, and makes it a build dependency
 # so the library is rebuilt if the source changes
 # call gb_DotnetLibrary_add_source,target,source
 define gb_DotnetLibrary_add_source
-$(call gb_DotnetLibrary_get_target,$(1)) : $(SRCDIR)/$(strip $(2))
-$(call gb_DotnetLibrary_add_items,$(1),<Compile Include="$(SRCDIR)/$(strip 
$(2))"/>)
+$(call gb_DotnetLibrary_get_target,$(1)) : $(call 
gb_DotnetLibrary__ensure_absolute,$(strip $(2)))
+$(call gb_DotnetLibrary_add_items,$(1),<Compile Include="$(call 
gb_DotnetLibrary__ensure_absolute,$(strip $(2)))"/>)
 
 endef
 
 # Add source files to the project file
+# This adds them to the project, and makes it them build dependency
+# so the library is rebuilt if the sources change
 # call gb_DotnetLibrary_add_sources,target,sources
 define gb_DotnetLibrary_add_sources
 $(foreach source,$(2),$(call gb_DotnetLibrary_add_source,$(1),$(source)))
 
 endef
 
+# Add one generated source file to the project file,
+# This is not marked as makefile build dependency,
+# so the library is NOT rebuilt if this source changes
+# Useful for things like source globs supported by .net projects
+# call gb_DotnetLibrary_add_generated_source,target,source
+define gb_DotnetLibrary_add_generated_source
+$(call gb_DotnetLibrary_add_items,$(1),<Compile Include="$(call 
gb_DotnetLibrary__ensure_absolute,$(strip $(2)))"/>)
+
+endef
+
+# Add generated source files to the project file,
+# These are not marked as makefile build dependencies,
+# so the library is NOT rebuilt if these sources change
+# Useful for things like source globs supported by .net projects
+# call gb_DotnetLibrary_add_generated_sources,target,sources
+define gb_DotnetLibrary_add_generated_sources
+$(foreach source,$(2),$(call 
gb_DotnetLibrary_add_generated_source,$(1),$(source)))
+
+endef
+
 # Link to a DotnetLibrary_CsLibrary target
-# call gb_DotnetLibrary_link_cs_project,target,project
-define gb_DotnetLibrary_link_cs_project
+# call gb_DotnetLibrary_link_cs_library,target,library
+define gb_DotnetLibrary_link_cs_library
 $(call gb_DotnetLibrary_get_target,$(1)) : $(call 
gb_DotnetLibrary_get_target,$(strip $(2)))
 $(call gb_DotnetLibrary_add_items,$(1),<ProjectReference Include="$(call 
gb_DotnetLibrary_get_workdir,$(strip $(2)))/$(strip $(2)).csproj"/>)
 
 endef
 
 # Link to a DotnetLibrary_FsLibrary target
-# call gb_DotnetLibrary_link_fs_project,target,project
-define gb_DotnetLibrary_link_fs_project
+# call gb_DotnetLibrary_link_fs_library,target,library
+define gb_DotnetLibrary_link_fs_library
 $(call gb_DotnetLibrary_get_target,$(1)) : $(call 
gb_DotnetLibrary_get_target,$(strip $(2)))
 $(call gb_DotnetLibrary_add_items,$(1),<ProjectReference Include="$(call 
gb_DotnetLibrary_get_workdir,$(strip $(2)))/$(strip $(2)).fsproj"/>)
 
 endef
 
 # Link to a DotnetLibrary_VbLibrary target
-# call gb_DotnetLibrary_link_vb_project,target,project
-define gb_DotnetLibrary_link_vb_project
+# call gb_DotnetLibrary_link_vb_library,target,library
+define gb_DotnetLibrary_link_vb_library
 $(call gb_DotnetLibrary_get_target,$(1)) : $(call 
gb_DotnetLibrary_get_target,$(strip $(2)))
 $(call gb_DotnetLibrary_add_items,$(1),<ProjectReference Include="$(call 
gb_DotnetLibrary_get_workdir,$(strip $(2)))/$(strip $(2)).vbproj"/>)
 

Reply via email to