From e597e712b6e1c199a6241df3c29f35baeef9f457 Mon Sep 17 00:00:00 2001 From: RMZeroFour Date: Wed, 12 Jun 2024 13:56:05 +0530 Subject: [PATCH] .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 Reviewed-by: Hossein --- 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(-) create mode 100644 codemaker/Executable_netmaker.mk create mode 100644 codemaker/source/netmaker/csharpfile.hxx create mode 100644 codemaker/source/netmaker/netmaker.cxx create mode 100644 codemaker/source/netmaker/netoptions.cxx create mode 100644 codemaker/source/netmaker/netoptions.hxx create mode 100644 codemaker/source/netmaker/netproduce.cxx create mode 100644 codemaker/source/netmaker/netproduce.hxx create mode 100644 net_ure/CustomTarget_net_oootypes.mk create mode 100644 net_ure/CustomTarget_net_uretypes.mk create mode 100644 net_ure/DotnetLibrary_net_oootypes.mk create mode 100644 net_ure/DotnetLibrary_net_uretypes.mk 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 +#include +#include +#include + +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 << '\n'; + 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 + +#include +#include + +#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 << '\n'; + return EXIT_FAILURE; + } + catch (const ::CannotDumpException& e) + { + std::cerr << "ERROR: Could not dump as " << e.getMessage() << '\n'; + return EXIT_FAILURE; + } + catch (const unoidl::NoSuchFileException& e) + { + std::cerr << "ERROR: No such file " << e.getUri() << '\n'; + return EXIT_FAILURE; + } + catch (const unoidl::FileFormatException& e) + { + std::cerr << "ERROR: Bad format of " << e.getUri() << ", '" << e.getDetail() << "'\n"; + return EXIT_FAILURE; + } + catch (const std::exception& e) + { + std::cerr << "ERROR: " << e.what() << '\n'; + 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 +#include + +#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 ] + [-X|--extra-types <.rdb file>] + -O|--output-dir + + +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 + 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 + 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 +#include + +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 +#include + +#include +#include +#include +#include + +#include "netproduce.hxx" +#include "csharpfile.hxx" + +namespace +{ +const std::unordered_set 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 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 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 entity; + rtl::Reference 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(entity.get())); + break; + + case codemaker::UnoType::Sort::PlainStruct: + producePlainStruct(name, + dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::PolymorphicStructTemplate: + producePolyStruct( + name, dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::Exception: + produceException(name, dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::Interface: + produceInterface(name, dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::Typedef: + produceTypedef(name, dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::ConstantGroup: + produceConstantGroup(name, + dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::SingleInterfaceBasedService: + produceService( + name, dynamic_cast(entity.get())); + break; + + case codemaker::UnoType::Sort::InterfaceBasedSingleton: + produceSingleton( + name, dynamic_cast(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(entity.get())); + break; + + default: + break; + } + } +} + +void NetProducer::produceModule(std::string_view name, + const rtl::Reference& 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& entity) +{ + CSharpFile file(m_outputDir, name); + + if (m_verbose) + std::cout << "[enum] " << name << " -> " << file.getPath() << '\n'; + 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& 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() << '\n'; + 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 baseFields; + { + OUString baseTypeName = base; + while (!baseTypeName.isEmpty()) + { + rtl::Reference 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 ref( + dynamic_cast(baseEntity.get())); + for (const auto& member : ref->getDirectMembers()) + baseFields.emplace_back(member); + + baseTypeName = ref->getDirectBase(); + } + } + + std::vector 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& 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() << '\n'; + 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& 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() << '\n'; + 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 baseFields; + { + OUString baseTypeName = base; + while (!baseTypeName.isEmpty()) + { + rtl::Reference baseEntity; + if (m_manager->getSort(baseTypeName, &baseEntity) + != codemaker::UnoType::Sort::Exception) + throw CannotDumpException("'" + b2u(name) + "' base type '" + baseTypeName + + "' is not an exception"); + + rtl::Reference ref( + dynamic_cast(baseEntity.get())); + for (const auto& member : ref->getDirectMembers()) + baseFields.emplace_back(member); + + baseTypeName = ref->getDirectBase(); + } + } + + std::vector 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& 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() << '\n'; + 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& entity) +{ + OString type = u2b(entity->getType()); + + produceType(getBaseUnoName(type)); + m_typedefs.emplace(name, type); + + if (m_verbose) + std::cout << "[typedef] " << name << " = " << type << '\n'; +} + +void NetProducer::produceConstantGroup(std::string_view name, + const rtl::Reference& entity) +{ + CSharpFile file(m_outputDir, name); + + if (m_verbose) + std::cout << "[constants] " << name << " -> " << file.getPath() << '\n'; + 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& entity) +{ + CSharpFile file(m_outputDir, name); + + if (m_verbose) + std::cout << "[service] " << name << " -> " << file.getPath() << '\n'; + 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 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()"); + } + 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& entity) +{ + CSharpFile file(m_outputDir, name); + + if (m_verbose) + std::cout << "[singleton] " << name << " -> " << file.getPath() << '\n'; + 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 +#include + +#include +#include +#include + +#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& cursor); + void produceEnum(std::string_view name, const rtl::Reference& entity); + void producePlainStruct(std::string_view name, + const rtl::Reference& entity); + void + producePolyStruct(std::string_view name, + const rtl::Reference& entity); + void produceException(std::string_view name, + const rtl::Reference& entity); + void produceInterface(std::string_view name, + const rtl::Reference& entity); + void produceTypedef(std::string_view name, const rtl::Reference& entity); + void produceConstantGroup(std::string_view name, + const rtl::Reference& entity); + void produceService(std::string_view name, + const rtl::Reference& entity); + void produceSingleton(std::string_view name, + const rtl::Reference& entity); + + OString getNetName(std::string_view name); + OString getNetName(std::u16string_view name); + +private: + rtl::Reference m_manager; + + std::unordered_set m_startingTypes; + std::unordered_set m_typesProduced; + std::unordered_map 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,\ + net_oootypes \ + 0.1.0 \ + LibreOffice \ + LibreOffice datatypes for the .NET language UNO binding. \ +)) + +# 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,\ + net_uretypes \ + 0.1.0 \ + LibreOffice \ + UNO runtime datatypes for the .NET language UNO binding. \ +)) + +# 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),) +$(call gb_DotnetLibrary_get_target,$(1)) : $(call gb_DotnetLibrary__ensure_absolute,$(strip $(2))) +$(call gb_DotnetLibrary_add_items,$(1),) 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),) + +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),) 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),) 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),)