Files
Stephan Bergmann 0e2ad911d9 Avoid deprecated std::filesystem::path::string
...and std::filesystem::path::generic_string, which at least GCC 17 trunk
libstdc++ now flags for C++26 (--with-latest-c++) as

> In file included from engine/codemaker/source/netmaker/netproduce.cxx:19:
> engine/codemaker/source/netmaker/csharpfile.hxx:28:53: error: 'string' is deprecated: use 'display_string' or 'native_encoded_string' instead [-Werror,-Wdeprecated-declarations]
>    28 |     std::string getPath() const { return m_filePath.string(); }
>       |                                                     ^
> ~/gcc/inst/lib/gcc/x86_64-pc-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/fs_path.h:465:5: note: 'string' has been explicitly marked deprecated here
>   465 |     _GLIBCXX26_DEPRECATED_SUGGEST("display_string' or 'native_encoded_string")
>       |     ^
> ~/gcc/inst/lib/gcc/x86_64-pc-linux-gnu/17.0.0/../../../../include/c++/17.0.0/x86_64-pc-linux-gnu/bits/c++config.h:160:45: note: expanded from macro '_GLIBCXX26_DEPRECATED_SUGGEST'
>   160 | # define _GLIBCXX26_DEPRECATED_SUGGEST(ALT) _GLIBCXX_DEPRECATED_SUGGEST(ALT)
>       |                                             ^
> ~/gcc/inst/lib/gcc/x86_64-pc-linux-gnu/17.0.0/../../../../include/c++/17.0.0/x86_64-pc-linux-gnu/bits/c++config.h:112:19: note: expanded from macro '_GLIBCXX_DEPRECATED_SUGGEST'
>   112 |   __attribute__ ((__deprecated__ ("use '" ALT "' instead")))
>       |                   ^

since
<https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=341ad729c2c93b9f01854913b0404e4fc8958512>
"libstdc++: Add (generic_)display_string and (generic_)native_encoded_string to
filesystem::path".

Occurrences that are obviously meant for display purposes are #if-conditionally
replaced with the new display_string, while all other occurrences are left using
the (non-deprecated) [generic_]string<char> template instantiation, matching the
original code.  (Where, for C++26, the latter could also be replaced with the
new [generic_]native_encoded_string, aka [generic_]system_encoded_string; see
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2319r5.html>
"Prevent path presentation problems" and
<https://cplusplus.github.io/LWG/issue4512> "The system_encoded_string() and
generic_system_encoded_string() member functions of std::filesystem::path are
misnamed" for details.)

Signed-off-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
Change-Id: I7c79c8522608550bb1e0f4cb8e4824480952446b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/3987
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit f320d95d89ecc9bea81182795969f83b05ec903c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206226
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-06-09 09:52:05 +02:00

679 lines
20 KiB
C++

/* -*- 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/.
*/
// Implementation of Rust file generation and module management
#include "rustfile.hxx"
#include <algorithm>
#include <filesystem>
#include <iostream>
#include <vector>
#include <functional>
#include <codemaker/global.hxx>
RustFile::RustFile(std::string_view directory, std::string_view typeName)
: m_filePath(createFilePath(directory, typeName))
, m_indentLevel(0)
{
}
std::string RustFile::getPath() const
{
#if defined __cpp_lib_format_path
return m_filePath.display_string();
#else
return m_filePath.string<char>();
#endif
}
void RustFile::openFile()
{
std::filesystem::create_directories(m_filePath.parent_path());
createModFiles();
addFileToModDeclaration();
m_fileStream.open(m_filePath, std::fstream::out | std::fstream::trunc);
m_indentLevel = 0;
}
void RustFile::closeFile() { m_fileStream.close(); }
void RustFile::createLibFile(const std::filesystem::path& outputDir)
{
// Create generated/rustmaker directory first, then src directory inside it
std::filesystem::path generatedDir = outputDir / "generated";
std::filesystem::path rustmakerDir = generatedDir / "rustmaker";
std::filesystem::path srcDir = rustmakerDir / "src";
std::filesystem::create_directories(srcDir);
std::filesystem::path libFile = srcDir / "lib.rs";
if (!std::filesystem::exists(libFile))
{
std::ofstream libStream(libFile);
libStream << "//! Generated Rust bindings for LibreOffice UNO types\n";
libStream << "//! \n";
libStream << "//! This crate contains automatically generated Rust bindings\n";
libStream << "//! for LibreOffice UNO (Universal Network Objects) types.\n\n";
libStream.close();
}
}
void RustFile::createCargoFile(const std::filesystem::path& outputDir, const std::string& crateName)
{
// Create Cargo.toml in the generated/rustmaker directory
std::filesystem::path generatedDir = outputDir / "generated";
std::filesystem::path rustmakerDir = generatedDir / "rustmaker";
std::filesystem::path cargoFile = rustmakerDir / "Cargo.toml";
if (!std::filesystem::exists(cargoFile))
{
std::ofstream cargoStream(cargoFile);
cargoStream << "[package]\n";
cargoStream << "name = \"" << crateName << "\"\n";
cargoStream << "version = \"0.1.0\"\n";
cargoStream << "edition = \"2024\"\n";
cargoStream << "\n";
cargoStream << "[lib]\n";
cargoStream << "name = \"rustmaker\"\n";
// Support both dynamic and static linking
cargoStream << "crate-type = [\"cdylib\", \"rlib\"]\n";
cargoStream << "description = \"Generated Rust bindings for LibreOffice UNO types\"\n\n";
cargoStream << "[dependencies]\n";
cargoStream << "# UNO runtime dependencies\n";
cargoStream << "rust_uno = { path = \"../../../../rust_uno\" }\n";
cargoStream.close();
}
}
void RustFile::createGeneratedModFile(const std::filesystem::path& outputDir)
{
// Create generated directory and its mod.rs file
std::filesystem::path generatedDir = outputDir / "generated";
std::filesystem::create_directories(generatedDir);
std::filesystem::path modFile = generatedDir / "mod.rs";
if (!std::filesystem::exists(modFile))
{
std::ofstream modStream(modFile);
modStream << "//! Generated opaque bindings for LibreOffice UNO types\n";
modStream << "//!\n";
modStream << "//! This module contains both Rust and C++ opaque bindings\n";
modStream << "//! auto-generated by rustmaker\n\n";
modStream << "// Include the generated rustmaker bindings directly\n";
modStream << "// The rustmaker directory contains a complete Rust project structure\n";
modStream << "// We include its lib.rs as a module here\n";
modStream << "#[path = \"rustmaker/src/lib.rs\"]\n";
modStream << "pub mod rustmaker;\n\n";
modStream << "// Re-export the UNO namespace for easier access\n";
modStream << "pub use rustmaker::com;\n\n";
modStream << "// Note: cpp_rustmaker directory contains C++ bridge files\n";
modStream << "// These are compiled separately and linked via build.rs\n";
modStream.close();
}
}
void RustFile::finalizeModFiles(const std::filesystem::path& outputDir)
{
// Find all directories in the rustmaker source tree and update their mod.rs files
std::filesystem::path rustmakerSrc = outputDir / "generated" / "rustmaker" / "src";
if (!std::filesystem::exists(rustmakerSrc))
{
return;
}
// Start processing from the rustmaker src directory
processDirectoryRecursively(rustmakerSrc);
}
RustFile& RustFile::beginBlock()
{
beginLine();
append("{");
endLine();
++m_indentLevel;
return *this;
}
RustFile& RustFile::endBlock()
{
--m_indentLevel;
beginLine();
append("}");
endLine();
return *this;
}
// Text output methods
RustFile& RustFile::beginLine()
{
for (int i = 0; i < m_indentLevel; i++)
{
m_fileStream << " ";
}
return *this;
}
RustFile& RustFile::extraIndent()
{
m_fileStream << " ";
return *this;
}
RustFile& RustFile::append(std::string_view item)
{
m_fileStream << item;
return *this;
}
RustFile& RustFile::append(std::u16string_view item)
{
m_fileStream << u2b(item);
return *this;
}
RustFile& RustFile::endLine()
{
m_fileStream << '\n';
return *this;
}
void RustFile::createModFiles()
{
std::filesystem::path outputRoot = findOutputRoot();
// Create lib and cargo files only once at the proper root level
// outputRoot should be the rustmaker directory, so we pass its parent to the create functions
// which will then create the rustmaker directory and put files inside it
createGeneratedModFile(
outputRoot.parent_path().parent_path()); // Create generated/mod.rs at top level
createLibFile(
outputRoot.parent_path().parent_path()); // Create in the directory containing generated/
createCargoFile(outputRoot.parent_path().parent_path(), "rustmaker");
// Build path hierarchy from file location up to project root
std::filesystem::path workingPath = m_filePath.parent_path();
std::vector<std::filesystem::path> pathsToProcess;
// Collect all directory paths from current file up to root
while (workingPath != outputRoot && workingPath.has_parent_path())
{
pathsToProcess.push_back(workingPath);
workingPath = workingPath.parent_path();
}
// Process paths from root down to ensure proper mod.rs structure
std::reverse(pathsToProcess.begin(), pathsToProcess.end());
for (const auto& path : pathsToProcess)
{
ensureModFileExists(path);
}
}
// Complex path resolution - walks up directory tree to find project root
std::filesystem::path RustFile::findOutputRoot()
{
std::filesystem::path currentPath = m_filePath.parent_path();
// Walk up until we find a directory structure indicating project root
while (currentPath.has_parent_path() && currentPath != currentPath.parent_path())
{
if (currentPath.filename() == "src")
{
return currentPath.parent_path(); // Return rustmaker directory
}
// Look for rustmaker directory as the project root
if (currentPath.filename() == "rustmaker")
{
return currentPath;
}
// Look for existing src/ subdirectory as root indicator
std::filesystem::path srcDir = currentPath / "src";
if (std::filesystem::exists(srcDir))
{
return currentPath;
}
currentPath = currentPath.parent_path();
}
// Fallback: find parent of src directory (which should be rustmaker)
std::filesystem::path result = m_filePath.parent_path();
while (result.has_parent_path() && result.filename() != "src")
{
result = result.parent_path();
}
if (result.filename() == "src")
{
return result.parent_path(); // Return rustmaker directory
}
return result; // Default case
}
void RustFile::addFileToModDeclaration()
{
std::filesystem::path parentDir = m_filePath.parent_path();
std::filesystem::path modFile = parentDir / "mod.rs";
std::string fileName = m_filePath.stem().string<char>(); // Get filename without extension
if (std::filesystem::exists(modFile))
{
addModuleDeclaration(modFile, fileName);
}
}
void RustFile::ensureModFileExists(const std::filesystem::path& dirPath)
{
// Skip mod.rs creation for the src directory itself
if (dirPath.filename() == "src")
{
return;
}
std::filesystem::path modFile = dirPath / "mod.rs";
if (!std::filesystem::exists(modFile))
{
// Create mod.rs file with proper structure
std::ofstream modStream(modFile);
modStream << "// Generated mod.rs file\n\n";
modStream << "#[allow(non_snake_case)]\n";
modStream.close();
}
// After ensuring the mod.rs exists, collect all subdirectories and modules
// to automatically add pub use ::* patterns
std::vector<std::string> childModules;
// Scan directory for subdirectories (modules) and .rs files
if (std::filesystem::exists(dirPath))
{
try
{
for (const auto& entry : std::filesystem::directory_iterator(dirPath))
{
if (entry.is_directory())
{
std::string dirName = entry.path().filename().string<char>();
childModules.push_back(dirName);
}
else if (entry.is_regular_file() && entry.path().extension() == ".rs")
{
std::string fileName = entry.path().stem().string<char>();
if (fileName != "mod") // Don't include mod.rs itself
{
childModules.push_back(fileName);
}
}
}
}
catch (const std::filesystem::filesystem_error&)
{
// Handle permission errors gracefully
}
}
// Add module declarations and pub use statements for all child modules
for (const std::string& moduleName : childModules)
{
addModuleDeclaration(modFile, moduleName);
}
// Recursively ensure parent modules are properly declared
if (dirPath.has_parent_path())
{
std::filesystem::path parentDir = dirPath.parent_path();
std::filesystem::path outputRoot = findOutputRoot();
if (parentDir >= outputRoot)
{
std::string moduleName = dirPath.filename().string<char>();
if (parentDir.filename() == "src")
{
std::filesystem::path libFile = parentDir / "lib.rs";
if (std::filesystem::exists(libFile))
{
addModuleDeclaration(libFile, moduleName);
}
}
else if (parentDir == outputRoot)
{
std::filesystem::path libFile = parentDir / "src" / "lib.rs";
if (std::filesystem::exists(libFile))
{
addModuleDeclaration(libFile, moduleName);
}
}
else
{
ensureModFileExists(parentDir);
std::filesystem::path parentModFile = parentDir / "mod.rs";
if (std::filesystem::exists(parentModFile))
{
addModuleDeclaration(parentModFile, moduleName);
}
}
}
}
}
void RustFile::addModuleDeclaration(const std::filesystem::path& modFile,
const std::string& moduleName)
{
// Read existing content to check for duplicates and organize sections
std::ifstream inFile(modFile);
std::string line;
std::vector<std::string> pubModLines;
std::vector<std::string> otherLines;
bool foundModDeclaration = false;
while (std::getline(inFile, line))
{
if (line.find("pub mod " + moduleName + ";") != std::string::npos)
{
foundModDeclaration = true;
}
if (line.find("pub mod ") == 0)
{
pubModLines.push_back(line);
}
else if (!(line.find("pub use ") == 0 && line.find("::*;") != std::string::npos))
{
// Skip old pub use re-export lines, include everything else
otherLines.push_back(line);
}
}
inFile.close();
// If module declaration is already declared, return
if (foundModDeclaration)
{
return;
}
// Add new declarations if not found
if (!foundModDeclaration)
{
pubModLines.push_back("pub mod " + moduleName + ";");
}
// Re-export generation removed - causes unused import warnings
// Rewrite the file with organized structure
std::ofstream outFile(modFile, std::ios::trunc);
// Write other lines first (comments, etc.)
for (const auto& otherLine : otherLines)
{
// Skip re-export comment lines
if (otherLine.find("// Re-export all items from child modules") == std::string::npos)
{
outFile << otherLine << "\n";
}
}
// Write all pub mod declarations
for (const auto& modLine : pubModLines)
{
outFile << modLine << "\n";
}
// Re-export section removed - causes unused import warnings
outFile.close();
}
// Converts UNO type name to file path (dots become path separators)
std::filesystem::path RustFile::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 /= "rustmaker"; // Add rustmaker subdirectory for generated types
path /= "src";
path /= subdir + ".rs";
return path;
}
// CppFile implementation
CppFile::CppFile(std::string_view directory, std::string_view typeName)
: m_filePath(createCppFilePath(directory, typeName))
, m_indentLevel(0)
{
}
CppFile::CppFile(std::string_view directory, std::string_view typeName, std::string_view extension)
: m_filePath(createCppFilePath(directory, typeName, extension))
, m_indentLevel(0)
{
}
std::string CppFile::getPathString() const
{
#if defined __cpp_lib_format_path
return m_filePath.display_string();
#else
return m_filePath.string<char>();
#endif
}
std::filesystem::path CppFile::getPath() const { return m_filePath; }
std::string CppFile::getExtension() const { return m_filePath.extension().string<char>(); }
void CppFile::openFile()
{
std::filesystem::create_directories(m_filePath.parent_path());
m_fileStream.open(m_filePath, std::fstream::out | std::fstream::trunc);
m_indentLevel = 0;
}
void CppFile::openFileAppend()
{
std::filesystem::create_directories(m_filePath.parent_path());
m_fileStream.open(m_filePath, std::fstream::out | std::fstream::app);
m_indentLevel = 0;
}
void CppFile::closeFile() { m_fileStream.close(); }
CppFile& CppFile::beginBlock()
{
beginLine();
append("{");
endLine();
++m_indentLevel;
return *this;
}
CppFile& CppFile::endBlock()
{
--m_indentLevel;
beginLine();
append("}");
endLine();
return *this;
}
CppFile& CppFile::beginLine()
{
m_fileStream << std::string(4 * m_indentLevel, ' ');
return *this;
}
CppFile& CppFile::extraIndent()
{
m_fileStream << " ";
return *this;
}
CppFile& CppFile::append(std::string_view item)
{
m_fileStream << item;
return *this;
}
CppFile& CppFile::append(std::u16string_view item)
{
m_fileStream << u2b(item);
return *this;
}
CppFile& CppFile::endLine()
{
m_fileStream << std::endl;
return *this;
}
CppFile& CppFile::writeIncludes(const std::vector<std::string>& includes)
{
for (const auto& include : includes)
{
beginLine().append("#include ").append(include).endLine();
}
endLine();
return *this;
}
CppFile& CppFile::writeNamespaceBegin(const std::vector<std::string>& namespaces)
{
for (const auto& ns : namespaces)
{
beginLine().append("namespace ").append(ns).append(" {").endLine();
}
endLine();
return *this;
}
CppFile& CppFile::writeNamespaceEnd(const std::vector<std::string>& namespaces)
{
endLine();
for (auto it = namespaces.rbegin(); it != namespaces.rend(); ++it)
{
beginLine().append("} // namespace ").append(*it).endLine();
}
return *this;
}
std::filesystem::path CppFile::createCppFilePath(std::string_view dir, std::string_view type,
std::string_view extension)
{
std::filesystem::path outputDir(dir);
// For the special case of "rust_uno_bindings", create it directly in the output directory
if (type == "rust_uno_bindings")
{
return outputDir / (std::string(type) + std::string(extension));
}
std::filesystem::path basePath = outputDir;
std::string typeName(type);
// Extract interface name (part after last dot)
size_t lastDot = typeName.find_last_of('.');
std::string interfaceName
= (lastDot != std::string::npos) ? typeName.substr(lastDot + 1) : typeName;
// Create namespace directory path from the full type name
if (lastDot != std::string::npos)
{
// Convert namespace dots to path separators: com.sun.star.frame -> com/sun/star/frame/
std::string namespacePath = typeName.substr(0, lastDot);
// Replace dots with path separators
std::replace(namespacePath.begin(), namespacePath.end(), '.', '/');
// Build full path: cpp_rustmaker/com/sun/star/frame/XComponentLoader.hxx
return basePath / namespacePath / (interfaceName + std::string(extension));
}
else
{
// No namespace, put directly in cpp_rustmaker directory
return basePath / (interfaceName + std::string(extension));
}
}
void RustFile::processDirectoryRecursively(const std::filesystem::path& currentDir)
{
if (!std::filesystem::exists(currentDir) || !std::filesystem::is_directory(currentDir))
{
return;
}
// Skip the root src directory
if (currentDir.filename() == "src" && currentDir.parent_path().filename() == "rustmaker")
{
// Process subdirectories but don't create mod.rs for src itself
for (const auto& entry : std::filesystem::directory_iterator(currentDir))
{
if (entry.is_directory())
{
processDirectoryRecursively(entry.path());
}
}
return;
}
std::vector<std::string> childModules;
try
{
// Collect all subdirectories and .rs files
for (const auto& entry : std::filesystem::directory_iterator(currentDir))
{
if (entry.is_directory())
{
std::string dirName = entry.path().filename().string<char>();
childModules.push_back(dirName);
// Recursively process subdirectory
processDirectoryRecursively(entry.path());
}
else if (entry.is_regular_file() && entry.path().extension() == ".rs")
{
std::string fileName = entry.path().stem().string<char>();
if (fileName != "mod" && fileName != "lib") // Don't include mod.rs or lib.rs
{
childModules.push_back(fileName);
}
}
}
}
catch (const std::filesystem::filesystem_error&)
{
// Handle permission errors gracefully
return;
}
// Create/update mod.rs file if there are child modules
if (!childModules.empty())
{
std::filesystem::path modFile = currentDir / "mod.rs";
// Create the mod.rs file with all declarations
std::ofstream modStream(modFile, std::ios::trunc);
modStream << "// Generated mod.rs file\n\n";
modStream << "#[allow(non_snake_case)]\n";
for (const std::string& moduleName : childModules)
{
modStream << "pub mod " << moduleName << ";\n";
}
// Re-export section removed per request
modStream.close();
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */