2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-06-26 14:21:49 +00:00

refactor: rework search paths

This commit is contained in:
Jan
2024-10-27 19:21:47 +01:00
parent fd421c4784
commit be6c30c503
24 changed files with 1072 additions and 1066 deletions

View File

@ -1,9 +1,8 @@
#include "Linker.h"
#include "LinkerArgs.h"
#include "LinkerSearchPaths.h"
#include "LinkerPaths.h"
#include "ObjContainer/IPak/IPakWriter.h"
#include "ObjContainer/IWD/IWD.h"
#include "ObjContainer/SoundBank/SoundBankWriter.h"
#include "ObjWriting.h"
#include "SearchPath/SearchPaths.h"
@ -20,17 +19,143 @@
#include <filesystem>
#include <format>
#include <fstream>
#include <set>
#include <unordered_set>
namespace fs = std::filesystem;
namespace
{
class LinkerSearchPathContext
{
public:
explicit LinkerSearchPathContext(const ILinkerSearchPathBuilder& searchPathBuilder)
: m_search_path_builder(searchPathBuilder)
{
m_independent_search_paths = m_search_path_builder.BuildIndependentSearchPaths();
if (m_independent_search_paths)
m_search_paths.IncludeSearchPath(m_independent_search_paths.get());
}
[[nodiscard]] ISearchPath& GetSearchPaths()
{
return m_search_paths;
}
void LoadProjectSpecific(const std::string& projectName)
{
m_project_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProject(projectName);
if (m_project_specific_search_paths)
m_search_paths.IncludeSearchPath(m_project_specific_search_paths.get());
}
void UnloadProjectSpecific()
{
if (!m_project_specific_search_paths)
return;
m_search_paths.RemoveSearchPath(m_project_specific_search_paths.get());
m_project_specific_search_paths.reset();
}
void LoadGameSpecific(const std::string& projectName, const GameId game)
{
m_game_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProjectAndGame(projectName, game);
if (m_game_specific_search_paths)
m_search_paths.IncludeSearchPath(m_game_specific_search_paths.get());
}
void UnloadGameSpecific()
{
if (!m_game_specific_search_paths)
return;
m_search_paths.RemoveSearchPath(m_game_specific_search_paths.get());
m_game_specific_search_paths.reset();
}
private:
const ILinkerSearchPathBuilder& m_search_path_builder;
std::unique_ptr<ISearchPath> m_independent_search_paths;
std::unique_ptr<ISearchPath> m_project_specific_search_paths;
std::unique_ptr<ISearchPath> m_game_specific_search_paths;
SearchPaths m_search_paths;
};
class LinkerPathManager
{
public:
explicit LinkerPathManager(const LinkerArgs& args)
: m_linker_paths(ILinkerPaths::FromArgs(args)),
m_asset_paths(m_linker_paths->AssetSearchPaths()),
m_gdt_paths(m_linker_paths->GdtSearchPaths()),
m_source_paths(m_linker_paths->SourceSearchPaths())
{
}
std::unique_ptr<ILinkerPaths> m_linker_paths;
LinkerSearchPathContext m_asset_paths;
LinkerSearchPathContext m_gdt_paths;
LinkerSearchPathContext m_source_paths;
};
class PathProjectContext
{
public:
PathProjectContext(LinkerPathManager& paths, const std::string& projectName)
: m_paths(paths)
{
m_paths.m_asset_paths.LoadProjectSpecific(projectName);
m_paths.m_gdt_paths.LoadProjectSpecific(projectName);
m_paths.m_source_paths.LoadProjectSpecific(projectName);
}
~PathProjectContext()
{
m_paths.m_asset_paths.UnloadProjectSpecific();
m_paths.m_gdt_paths.UnloadProjectSpecific();
m_paths.m_source_paths.UnloadProjectSpecific();
}
PathProjectContext(const PathProjectContext& other) = delete;
PathProjectContext(PathProjectContext&& other) noexcept = delete;
PathProjectContext& operator=(const PathProjectContext& other) = delete;
PathProjectContext& operator=(PathProjectContext&& other) noexcept = delete;
private:
LinkerPathManager& m_paths;
};
class PathGameContext
{
public:
PathGameContext(LinkerPathManager& paths, const std::string& projectName, const GameId game)
: m_paths(paths)
{
m_paths.m_asset_paths.LoadGameSpecific(projectName, game);
m_paths.m_gdt_paths.LoadGameSpecific(projectName, game);
m_paths.m_source_paths.LoadGameSpecific(projectName, game);
}
~PathGameContext()
{
m_paths.m_asset_paths.UnloadGameSpecific();
m_paths.m_gdt_paths.UnloadGameSpecific();
m_paths.m_source_paths.UnloadGameSpecific();
}
PathGameContext(const PathGameContext& other) = delete;
PathGameContext(PathGameContext&& other) noexcept = delete;
PathGameContext& operator=(const PathGameContext& other) = delete;
PathGameContext& operator=(PathGameContext&& other) noexcept = delete;
private:
LinkerPathManager& m_paths;
};
} // namespace
class LinkerImpl final : public Linker
{
LinkerArgs m_args;
LinkerSearchPaths m_search_paths;
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
bool IncludeAdditionalZoneDefinitions(const std::string& initialFileName, ZoneDefinition& zoneDefinition, ISearchPath* sourceSearchPath) const
bool IncludeAdditionalZoneDefinitions(const std::string& initialFileName, ZoneDefinition& zoneDefinition, ISearchPath& sourceSearchPath) const
{
std::set<std::string> sourceNames;
sourceNames.emplace(initialFileName);
@ -50,7 +175,7 @@ class LinkerImpl final : public Linker
std::unique_ptr<ZoneDefinition> includeDefinition;
{
const auto definitionFileName = std::format("{}.zone", source);
const auto definitionStream = sourceSearchPath->Open(definitionFileName);
const auto definitionStream = sourceSearchPath.Open(definitionFileName);
if (!definitionStream.IsOpen())
{
std::cerr << std::format("Could not find zone definition file for project \"{}\".\n", source);
@ -80,11 +205,11 @@ class LinkerImpl final : public Linker
return true;
}
bool ReadAssetList(const std::string& zoneName, const GameId game, AssetList& assetList, ISearchPath* sourceSearchPath) const
bool ReadAssetList(LinkerPathManager& paths, const std::string& zoneName, const GameId game, AssetList& assetList) const
{
{
const auto assetListFileName = std::format("assetlist/{}.csv", zoneName);
const auto assetListStream = sourceSearchPath->Open(assetListFileName);
const auto assetListStream = paths.m_source_paths.GetSearchPaths().Open(assetListFileName);
if (assetListStream.IsOpen())
{
@ -102,7 +227,7 @@ class LinkerImpl final : public Linker
}
{
const auto zoneDefinition = ReadZoneDefinition(zoneName, sourceSearchPath);
const auto zoneDefinition = ReadZoneDefinition(paths, zoneName);
if (zoneDefinition)
{
@ -117,12 +242,12 @@ class LinkerImpl final : public Linker
return false;
}
bool IncludeAssetLists(ZoneDefinition& zoneDefinition, ISearchPath* sourceSearchPath) const
bool IncludeAssetLists(LinkerPathManager& paths, ZoneDefinition& zoneDefinition) const
{
for (const auto& assetListName : zoneDefinition.m_asset_lists)
{
AssetList assetList;
if (!ReadAssetList(assetListName, zoneDefinition.m_game, assetList, sourceSearchPath))
if (!ReadAssetList(paths, assetListName, zoneDefinition.m_game, assetList))
{
std::cerr << std::format("Failed to read asset list \"{}\"\n", assetListName);
return false;
@ -134,12 +259,13 @@ class LinkerImpl final : public Linker
return true;
}
std::unique_ptr<ZoneDefinition> ReadZoneDefinition(const std::string& targetName, ISearchPath* sourceSearchPath) const
std::unique_ptr<ZoneDefinition> ReadZoneDefinition(LinkerPathManager& paths, const std::string& targetName) const
{
auto& sourceSearchPath = paths.m_source_paths.GetSearchPaths();
std::unique_ptr<ZoneDefinition> zoneDefinition;
{
const auto definitionFileName = std::format("{}.zone", targetName);
const auto definitionStream = sourceSearchPath->Open(definitionFileName);
const auto definitionStream = sourceSearchPath.Open(definitionFileName);
if (!definitionStream.IsOpen())
{
std::cerr << std::format("Could not find zone definition file for target \"{}\".\n", targetName);
@ -159,13 +285,13 @@ class LinkerImpl final : public Linker
if (!IncludeAdditionalZoneDefinitions(targetName, *zoneDefinition, sourceSearchPath))
return nullptr;
if (!IncludeAssetLists(*zoneDefinition, sourceSearchPath))
if (!IncludeAssetLists(paths, *zoneDefinition))
return nullptr;
return zoneDefinition;
}
bool ProcessZoneDefinitionIgnores(const std::string& targetName, ZoneCreationContext& context, ISearchPath* sourceSearchPath) const
bool ProcessZoneDefinitionIgnores(LinkerPathManager& paths, const std::string& targetName, ZoneCreationContext& context) const
{
if (context.m_definition->m_ignores.empty())
return true;
@ -176,7 +302,7 @@ class LinkerImpl final : public Linker
continue;
std::vector<AssetListEntry> assetList;
if (!ReadAssetList(ignore, context.m_definition->m_game, context.m_ignored_assets, sourceSearchPath))
if (!ReadAssetList(paths, ignore, context.m_definition->m_game, context.m_ignored_assets))
{
std::cerr << std::format("Failed to read asset listing for ignoring assets of project \"{}\".\n", ignore);
return false;
@ -210,25 +336,21 @@ class LinkerImpl final : public Linker
return true;
}
std::unique_ptr<Zone> CreateZoneForDefinition(const std::string& targetName,
ZoneDefinition& zoneDefinition,
ISearchPath* assetSearchPath,
ISearchPath* gdtSearchPath,
ISearchPath* sourceSearchPath) const
std::unique_ptr<Zone> CreateZoneForDefinition(LinkerPathManager& paths, const std::string& targetName, ZoneDefinition& zoneDefinition) const
{
ZoneCreationContext context(&zoneDefinition, assetSearchPath);
if (!ProcessZoneDefinitionIgnores(targetName, context, sourceSearchPath))
ZoneCreationContext context(&zoneDefinition, &paths.m_asset_paths.GetSearchPaths());
if (!ProcessZoneDefinitionIgnores(paths, targetName, context))
return nullptr;
if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, gdtSearchPath))
if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, &paths.m_gdt_paths.GetSearchPaths()))
return nullptr;
const auto* creator = IZoneCreator::GetCreatorForGame(zoneDefinition.m_game);
return creator->CreateZoneForDefinition(context);
}
bool WriteZoneToFile(const std::string& projectName, Zone* zone) const
bool WriteZoneToFile(const LinkerPathManager& paths, const std::string& projectName, Zone* zone) const
{
const fs::path zoneFolderPath(m_args.GetOutputFolderPathForProject(projectName));
const fs::path zoneFolderPath(paths.m_linker_paths->BuildOutputFolderPath(projectName, zone->m_game->GetId()));
auto zoneFilePath(zoneFolderPath);
zoneFilePath.append(zone->m_name + ".ff");
@ -256,26 +378,21 @@ class LinkerImpl final : public Linker
return true;
}
bool BuildFastFile(const std::string& projectName,
const std::string& targetName,
ZoneDefinition& zoneDefinition,
SearchPaths& assetSearchPaths,
SearchPaths& gdtSearchPaths,
SearchPaths& sourceSearchPaths) const
bool BuildFastFile(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition) const
{
SoundBankWriter::OutputPath = fs::path(m_args.GetOutputFolderPathForProject(projectName));
SoundBankWriter::OutputPath = fs::path(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game));
const auto zone = CreateZoneForDefinition(targetName, zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths);
const auto zone = CreateZoneForDefinition(paths, targetName, zoneDefinition);
auto result = zone != nullptr;
if (zone)
result = WriteZoneToFile(projectName, zone.get());
result = WriteZoneToFile(paths, projectName, zone.get());
return result;
}
bool BuildIPak(const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths) const
bool BuildIPak(const LinkerPathManager& paths, const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths) const
{
const fs::path ipakFolderPath(m_args.GetOutputFolderPathForProject(projectName));
const fs::path ipakFolderPath(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game));
auto ipakFilePath(ipakFolderPath);
ipakFilePath.append(std::format("{}.ipak", zoneDefinition.m_name));
@ -309,46 +426,44 @@ class LinkerImpl final : public Linker
return true;
}
bool BuildReferencedTargets(const std::string& projectName, const std::string& targetName, const ZoneDefinition& zoneDefinition)
bool BuildProject(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName) const
{
return std::ranges::all_of(zoneDefinition.m_targets_to_build,
[this, &projectName, &targetName](const std::string& buildTargetName)
{
if (buildTargetName == targetName)
{
std::cerr << std::format("Cannot build target with same name: \"{}\"\n", targetName);
return false;
}
std::deque<std::string> targetsToBuild;
std::unordered_set<std::string> alreadyBuiltTargets;
std::cout << std::format("Building referenced target \"{}\"\n", buildTargetName);
return BuildProject(projectName, buildTargetName);
});
}
targetsToBuild.emplace_back(targetName);
bool BuildProject(const std::string& projectName, const std::string& targetName)
{
auto sourceSearchPaths = m_search_paths.GetSourceSearchPathsForProject(projectName);
const auto zoneDefinition = ReadZoneDefinition(targetName, &sourceSearchPaths);
if (!zoneDefinition)
return false;
auto result = true;
if (!zoneDefinition->m_assets.empty())
while (!targetsToBuild.empty())
{
const auto& gameName = GameId_Names[static_cast<unsigned>(zoneDefinition->m_game)];
auto assetSearchPaths = m_search_paths.GetAssetSearchPathsForProject(gameName, projectName);
auto gdtSearchPaths = m_search_paths.GetGdtSearchPathsForProject(gameName, projectName);
const auto currentTarget = std::move(targetsToBuild.front());
targetsToBuild.pop_front();
alreadyBuiltTargets.emplace(currentTarget);
result = result && BuildFastFile(projectName, targetName, *zoneDefinition, assetSearchPaths, gdtSearchPaths, sourceSearchPaths);
PathProjectContext projectContext(paths, projectName);
const auto zoneDefinition = ReadZoneDefinition(paths, targetName);
if (!zoneDefinition)
return false;
PathGameContext gameContext(paths, projectName, zoneDefinition->m_game);
if (!zoneDefinition->m_assets.empty())
{
if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition))
return false;
for (const auto& referencedTarget : zoneDefinition->m_targets_to_build)
{
if (alreadyBuiltTargets.find(referencedTarget) == alreadyBuiltTargets.end())
{
targetsToBuild.emplace_back(referencedTarget);
std::cout << std::format("Building referenced target \"{}\"\n", referencedTarget);
}
}
}
}
m_search_paths.UnloadProjectSpecificSearchPaths();
result = result && BuildReferencedTargets(projectName, targetName, *zoneDefinition);
return result;
return true;
}
bool LoadZones()
@ -434,11 +549,6 @@ class LinkerImpl final : public Linker
}
public:
LinkerImpl()
: m_search_paths(m_args)
{
}
bool Start(const int argc, const char** argv) override
{
auto shouldContinue = true;
@ -448,8 +558,7 @@ public:
if (!shouldContinue)
return true;
if (!m_search_paths.BuildProjectIndependentSearchPaths())
return false;
LinkerPathManager paths(m_args);
if (!LoadZones())
return false;
@ -465,7 +574,7 @@ public:
break;
}
if (!BuildProject(projectName, targetName))
if (!BuildProject(paths, projectName, targetName))
{
result = false;
break;
@ -476,6 +585,10 @@ public:
return result;
}
private:
LinkerArgs m_args;
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
};
std::unique_ptr<Linker> Linker::Create()

View File

@ -9,7 +9,6 @@
#include <filesystem>
#include <format>
#include <iostream>
#include <regex>
#include <type_traits>
namespace fs = std::filesystem;
@ -129,13 +128,7 @@ const CommandLineOption* const COMMAND_LINE_OPTIONS[]{
LinkerArgs::LinkerArgs()
: m_verbose(false),
m_base_folder_depends_on_project(false),
m_out_folder_depends_on_project(false),
m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>),
m_bin_pattern(R"(\?bin\?)"),
m_base_pattern(R"(\?base\?)"),
m_game_pattern(R"(\?game\?)"),
m_project_pattern(R"(\?project\?)")
m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>)
{
}
@ -172,76 +165,6 @@ void LinkerArgs::SetVerbose(const bool isVerbose)
ObjWriting::Configuration.Verbose = isVerbose;
}
std::string LinkerArgs::GetBasePathForProject(const std::string& projectName) const
{
return std::regex_replace(m_base_folder, m_project_pattern, projectName);
}
void LinkerArgs::SetDefaultBasePath()
{
const auto currentDir = fs::absolute(fs::current_path());
if (currentDir.filename() == "bin")
{
m_base_folder = currentDir.parent_path().string();
}
else
{
m_base_folder = currentDir.string();
}
}
std::set<std::string> LinkerArgs::GetProjectIndependentSearchPaths(const std::set<std::string>& set) const
{
std::set<std::string> out;
for (const auto& path : set)
{
if (path.find(PATTERN_GAME) != std::string::npos)
continue;
if (path.find(PATTERN_PROJECT) != std::string::npos)
continue;
if (path.find(PATTERN_BASE) != std::string::npos)
{
if (m_base_folder_depends_on_project)
continue;
out.emplace(std::regex_replace(path, m_base_pattern, m_base_folder));
}
else
{
out.emplace(path);
}
}
return out;
}
std::set<std::string> LinkerArgs::GetSearchPathsForProject(const std::set<std::string>& set, const std::string& gameName, const std::string& projectName) const
{
std::set<std::string> out;
const auto basePath = GetBasePathForProject(projectName);
for (const auto& path : set)
{
if (path.find(PATTERN_GAME) == std::string::npos && path.find(PATTERN_PROJECT) == std::string::npos
&& (!m_base_folder_depends_on_project || path.find(PATTERN_BASE) == std::string::npos))
{
continue;
}
fs::path p(std::regex_replace(std::regex_replace(std::regex_replace(std::regex_replace(path, m_project_pattern, projectName), m_game_pattern, gameName),
m_base_pattern,
basePath),
m_bin_pattern,
m_bin_folder));
out.emplace(p.make_preferred().string());
}
return out;
}
bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue)
{
shouldContinue = true;
@ -284,15 +207,13 @@ bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContin
if (m_argument_parser.IsOptionSpecified(OPTION_BASE_FOLDER))
m_base_folder = m_argument_parser.GetValueForOption(OPTION_BASE_FOLDER);
else
SetDefaultBasePath();
m_base_folder_depends_on_project = m_base_folder.find(PATTERN_GAME) != std::string::npos || m_base_folder.find(PATTERN_PROJECT) != std::string::npos;
m_base_folder = DEFAULT_BASE_FOLDER;
// --output-folder
if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER))
m_out_folder = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER);
else
m_out_folder = DEFAULT_OUTPUT_FOLDER;
m_out_folder_depends_on_project = m_out_folder.find(PATTERN_PROJECT) != std::string::npos;
// --asset-search-path
if (m_argument_parser.IsOptionSpecified(OPTION_ASSET_SEARCH_PATH))
@ -358,38 +279,3 @@ bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContin
return true;
}
std::string LinkerArgs::GetOutputFolderPathForProject(const std::string& projectName) const
{
return std::regex_replace(std::regex_replace(m_out_folder, m_project_pattern, projectName), m_base_pattern, GetBasePathForProject(projectName));
}
std::set<std::string> LinkerArgs::GetProjectIndependentAssetSearchPaths() const
{
return GetProjectIndependentSearchPaths(m_asset_search_paths);
}
std::set<std::string> LinkerArgs::GetProjectIndependentGdtSearchPaths() const
{
return GetProjectIndependentSearchPaths(m_gdt_search_paths);
}
std::set<std::string> LinkerArgs::GetProjectIndependentSourceSearchPaths() const
{
return GetProjectIndependentSearchPaths(m_source_search_paths);
}
std::set<std::string> LinkerArgs::GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName) const
{
return GetSearchPathsForProject(m_asset_search_paths, gameName, projectName);
}
std::set<std::string> LinkerArgs::GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName) const
{
return GetSearchPathsForProject(m_gdt_search_paths, gameName, projectName);
}
std::set<std::string> LinkerArgs::GetSourceSearchPathsForProject(const std::string& projectName) const
{
return GetSearchPathsForProject(m_source_search_paths, "", projectName);
}

View File

@ -1,22 +1,14 @@
#pragma once
#include "Utils/Arguments/ArgumentParser.h"
#include "Utils/ClassUtils.h"
#include "Zone/Zone.h"
#include <regex>
#include <set>
#include <vector>
class LinkerArgs
{
public:
static constexpr auto PATTERN_BIN = "?bin?";
static constexpr auto PATTERN_BASE = "?base?";
static constexpr auto PATTERN_GAME = "?game?";
static constexpr auto PATTERN_PROJECT = "?project?";
static constexpr auto DEFAULT_BASE_FOLDER = ".";
static constexpr auto DEFAULT_BASE_FOLDER_MOD_TOOLS = "..";
static constexpr auto DEFAULT_CACHE_FOLDER = "?base?/.oat/cache/?project?";
static constexpr auto DEFAULT_OUTPUT_FOLDER = "?base?/zone_out/?project?";
static constexpr auto DEFAULT_ASSET_SEARCH_PATH = "?bin?/raw/?game?;?base?/raw;?base?/raw/?game?;?base?/zone_raw/?project?";
static constexpr auto DEFAULT_GDT_SEARCH_PATH = "?base?/source_data;?base?/zone_raw/?project?/source_data";
@ -25,21 +17,6 @@ public:
LinkerArgs();
bool ParseArgs(int argc, const char** argv, bool& shouldContinue);
/**
* \brief Converts the output path specified by command line arguments to a path applies for the specified project.
* \param projectName The name of the project to resolve the path input for.
* \return An output path for the project based on the user input.
*/
_NODISCARD std::string GetOutputFolderPathForProject(const std::string& projectName) const;
_NODISCARD std::set<std::string> GetProjectIndependentAssetSearchPaths() const;
_NODISCARD std::set<std::string> GetProjectIndependentGdtSearchPaths() const;
_NODISCARD std::set<std::string> GetProjectIndependentSourceSearchPaths() const;
_NODISCARD std::set<std::string> GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName) const;
_NODISCARD std::set<std::string> GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName) const;
_NODISCARD std::set<std::string> GetSourceSearchPathsForProject(const std::string& projectName) const;
bool m_verbose;
std::vector<std::string> m_zones_to_load;
@ -48,8 +25,6 @@ public:
std::string m_bin_folder;
std::string m_base_folder;
std::string m_out_folder;
bool m_base_folder_depends_on_project;
bool m_out_folder_depends_on_project;
std::set<std::string> m_asset_search_paths;
std::set<std::string> m_gdt_search_paths;
@ -63,18 +38,7 @@ private:
static void PrintVersion();
void SetBinFolder(const char* argv0);
void SetVerbose(bool isVerbose);
_NODISCARD std::string GetBasePathForProject(const std::string& projectName) const;
void SetDefaultBasePath();
_NODISCARD std::set<std::string> GetProjectIndependentSearchPaths(const std::set<std::string>& set) const;
_NODISCARD std::set<std::string>
GetSearchPathsForProject(const std::set<std::string>& set, const std::string& gameName, const std::string& projectName) const;
ArgumentParser m_argument_parser;
std::regex m_bin_pattern;
std::regex m_base_pattern;
std::regex m_game_pattern;
std::regex m_project_pattern;
};

325
src/Linker/LinkerPaths.cpp Normal file
View File

@ -0,0 +1,325 @@
#include "LinkerPaths.h"
#include "SearchPath/IWD.h"
#include "SearchPath/SearchPathFilesystem.h"
#include "SearchPath/SearchPaths.h"
#include <cassert>
#include <cstdint>
#include <filesystem>
#include <format>
#include <iostream>
#include <sstream>
#include <type_traits>
namespace fs = std::filesystem;
namespace
{
enum class PathTemplateParameterType : std::uint8_t
{
BIN = 1 << 0,
BASE = 1 << 1,
PROJECT = 1 << 2,
GAME = 1 << 3,
};
class LinkerPathTemplate
{
static constexpr auto PATTERN_BIN = "?bin?";
static constexpr auto PATTERN_BASE = "?base?";
static constexpr auto PATTERN_GAME = "?game?";
static constexpr auto PATTERN_PROJECT = "?project?";
struct Pattern
{
const char* m_str;
PathTemplateParameterType m_type;
};
static constexpr Pattern PATTERNS[]{
{PATTERN_BIN, PathTemplateParameterType::BIN },
{PATTERN_BASE, PathTemplateParameterType::BASE },
{PATTERN_GAME, PathTemplateParameterType::GAME },
{PATTERN_PROJECT, PathTemplateParameterType::PROJECT},
};
public:
LinkerPathTemplate()
: m_parameter_type_flags(0u)
{
}
void CreateFromString(const std::string& templateString)
{
const auto templateStringLength = templateString.size();
auto partStart = 0u;
for (auto i = 0u; i < templateStringLength; i++)
{
if (templateString[i] != '?')
continue;
for (const auto& pattern : PATTERNS)
{
const auto patternLength = std::strlen(pattern.m_str);
if (templateString.compare(i, patternLength, pattern.m_str) == 0)
{
m_parts.emplace_back(templateString.substr(partStart, i - partStart));
m_parameters.emplace_back(pattern.m_type);
m_parameter_type_flags |= static_cast<std::underlying_type_t<PathTemplateParameterType>>(pattern.m_type);
i += patternLength;
partStart = i;
break;
}
}
}
if (partStart < templateStringLength)
m_parts.emplace_back(templateString.substr(partStart, templateStringLength - partStart));
}
[[nodiscard]] std::string
Render(const std::string& binDir, const std::string& baseDir, const std::string& projectName, const std::string& gameName) const
{
if (m_parts.empty())
return "";
if (m_parameters.empty())
return m_parts[0];
std::ostringstream ss;
ss << m_parts[0];
const auto partsCount = m_parts.size();
const auto parameterCount = m_parameters.size();
for (auto parameterIndex = 0u; parameterIndex < parameterCount; parameterIndex++)
{
switch (m_parameters[parameterIndex])
{
case PathTemplateParameterType::BIN:
ss << binDir;
break;
case PathTemplateParameterType::BASE:
ss << baseDir;
break;
case PathTemplateParameterType::PROJECT:
ss << projectName;
break;
case PathTemplateParameterType::GAME:
ss << gameName;
break;
default:
assert(false);
break;
}
if (parameterIndex + 1 < partsCount)
ss << m_parts[parameterIndex + 1];
}
return ss.str();
}
[[nodiscard]] bool CanRender(const std::underlying_type_t<PathTemplateParameterType> availableParameters) const
{
return (m_parameter_type_flags & ~availableParameters) == 0;
}
private:
std::vector<std::string> m_parts;
std::vector<PathTemplateParameterType> m_parameters;
std::underlying_type_t<PathTemplateParameterType> m_parameter_type_flags;
};
class LinkerSearchPathBuilder final : public ILinkerSearchPathBuilder
{
static constexpr auto INDEPENDENT_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE);
static constexpr auto PROJECT_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE)
| static_cast<unsigned>(PathTemplateParameterType::PROJECT);
static constexpr auto GAME_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE)
| static_cast<unsigned>(PathTemplateParameterType::PROJECT) | static_cast<unsigned>(PathTemplateParameterType::GAME);
public:
LinkerSearchPathBuilder(const char* typeName, const std::string& binDir, const std::string& baseDir)
: m_type_name(typeName),
m_bin_dir(binDir),
m_base_dir(baseDir)
{
}
void BuildFromArgs(const std::set<std::string>& templates)
{
m_templates.reserve(templates.size());
for (const auto& templateString : templates)
{
LinkerPathTemplate templateStruct;
templateStruct.CreateFromString(templateString);
m_templates.emplace_back(std::move(templateStruct));
}
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildIndependentSearchPaths() const override
{
SearchPaths searchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (curTemplate.CanRender(INDEPENDENT_MASK))
{
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, std::string(), std::string());
if (AddSearchPath(searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProject(const std::string& projectName) const override
{
SearchPaths searchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (!curTemplate.CanRender(INDEPENDENT_MASK) && curTemplate.CanRender(PROJECT_MASK))
{
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, std::string());
if (AddSearchPath(searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const override
{
SearchPaths searchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (!curTemplate.CanRender(PROJECT_MASK) && curTemplate.CanRender(GAME_MASK))
{
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast<unsigned>(game)]);
if (AddSearchPath(searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
private:
bool AddSearchPath(SearchPaths& searchPaths, const std::string& path) const
{
if (!fs::is_directory(path))
{
std::cout << std::format("Adding {} search path (Not found): {}\n", m_type_name, path);
return false;
}
std::cout << std::format("Adding {} search path: {}\n", m_type_name, path);
searchPaths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(path));
return true;
}
const char* m_type_name;
std::vector<LinkerPathTemplate> m_templates;
const std::string& m_bin_dir;
const std::string& m_base_dir;
};
class LinkerPaths final : public ILinkerPaths
{
public:
LinkerPaths(std::string binDir,
std::string baseDir,
LinkerSearchPathBuilder assetSearchPaths,
LinkerSearchPathBuilder gdtSearchPaths,
LinkerSearchPathBuilder sourceSearchPaths,
LinkerPathTemplate cacheTemplate,
LinkerPathTemplate outTemplate)
: m_bin_dir(std::move(binDir)),
m_base_dir(std::move(baseDir)),
m_asset_search_paths(std::move(assetSearchPaths)),
m_gdt_search_paths(std::move(gdtSearchPaths)),
m_source_search_paths(std::move(sourceSearchPaths)),
m_cache_template(std::move(cacheTemplate)),
m_out_template(std::move(outTemplate))
{
}
[[nodiscard]] const ILinkerSearchPathBuilder& AssetSearchPaths() const override
{
return m_asset_search_paths;
}
[[nodiscard]] const ILinkerSearchPathBuilder& GdtSearchPaths() const override
{
return m_gdt_search_paths;
}
[[nodiscard]] const ILinkerSearchPathBuilder& SourceSearchPaths() const override
{
return m_source_search_paths;
}
[[nodiscard]] std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const override
{
return m_cache_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast<unsigned>(game)]);
}
[[nodiscard]] std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const override
{
return m_out_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast<unsigned>(game)]);
}
private:
std::string m_bin_dir;
std::string m_base_dir;
LinkerSearchPathBuilder m_asset_search_paths;
LinkerSearchPathBuilder m_gdt_search_paths;
LinkerSearchPathBuilder m_source_search_paths;
LinkerPathTemplate m_cache_template;
LinkerPathTemplate m_out_template;
};
} // namespace
std::unique_ptr<ILinkerPaths> ILinkerPaths::FromArgs(const LinkerArgs& args)
{
LinkerSearchPathBuilder assetSearchPaths("asset", args.m_bin_folder, args.m_base_folder);
assetSearchPaths.BuildFromArgs(args.m_asset_search_paths);
LinkerSearchPathBuilder gdtSearchPaths("gdt", args.m_bin_folder, args.m_base_folder);
gdtSearchPaths.BuildFromArgs(args.m_gdt_search_paths);
LinkerSearchPathBuilder sourceSearchPaths("source", args.m_bin_folder, args.m_base_folder);
sourceSearchPaths.BuildFromArgs(args.m_source_search_paths);
LinkerPathTemplate cacheTemplate;
cacheTemplate.CreateFromString(args.DEFAULT_CACHE_FOLDER);
LinkerPathTemplate outTemplate;
outTemplate.CreateFromString(args.m_out_folder);
return std::make_unique<LinkerPaths>(args.m_bin_folder,
args.m_base_folder,
std::move(assetSearchPaths),
std::move(gdtSearchPaths),
std::move(sourceSearchPaths),
std::move(cacheTemplate),
std::move(outTemplate));
}

74
src/Linker/LinkerPaths.h Normal file
View File

@ -0,0 +1,74 @@
#pragma once
#include "Game/IGame.h"
#include "LinkerArgs.h"
#include "SearchPath/ISearchPath.h"
#include <memory>
class ILinkerSearchPathBuilder
{
public:
ILinkerSearchPathBuilder() = default;
virtual ~ILinkerSearchPathBuilder() = default;
ILinkerSearchPathBuilder(const ILinkerSearchPathBuilder& other) = default;
ILinkerSearchPathBuilder(ILinkerSearchPathBuilder&& other) noexcept = default;
ILinkerSearchPathBuilder& operator=(const ILinkerSearchPathBuilder& other) = default;
ILinkerSearchPathBuilder& operator=(ILinkerSearchPathBuilder&& other) noexcept = default;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildIndependentSearchPaths() const = 0;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProject(const std::string& projectName) const = 0;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const = 0;
};
class ILinkerPaths
{
public:
ILinkerPaths() = default;
virtual ~ILinkerPaths() = default;
ILinkerPaths(const ILinkerPaths& other) = default;
ILinkerPaths(ILinkerPaths&& other) noexcept = default;
ILinkerPaths& operator=(const ILinkerPaths& other) = default;
ILinkerPaths& operator=(ILinkerPaths&& other) noexcept = default;
/**
* \brief Creates linker search paths based on templates from user specified args.
* \param args The user specified args.
* \return Linker search paths based on user specified templates.
*/
static std::unique_ptr<ILinkerPaths> FromArgs(const LinkerArgs& args);
/**
* \brief Grants access to the builder for asset search paths.
* \return A builder instance for building asset search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& AssetSearchPaths() const = 0;
/**
* \brief Grants access to the builder for gdt search paths.
* \return A builder instance for building gdt search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& GdtSearchPaths() const = 0;
/**
* \brief Grants access to the builder for source search paths.
* \return A builder instance for building source search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& SourceSearchPaths() const = 0;
/**
* \brief Builds the cache path based on the specified information.
* \param projectName The name of the project to resolve the path input for.
* \param game The game to resolve the path input for.
* \return A cache path based on the input and preconfigured template.
*/
[[nodiscard]] virtual std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const = 0;
/**
* \brief Builds the output path based on the specified information.
* \param projectName The name of the project to resolve the path input for.
* \param game The game to resolve the path input for.
* \return An output path based on the input and preconfigured template.
*/
[[nodiscard]] virtual std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const = 0;
};

View File

@ -1,190 +0,0 @@
#include "LinkerSearchPaths.h"
#include "ObjContainer/IWD/IWD.h"
#include "ObjLoading.h"
#include "SearchPath/SearchPathFilesystem.h"
#include <filesystem>
#include <format>
#include <iostream>
namespace fs = std::filesystem;
LinkerSearchPaths::LinkerSearchPaths(const LinkerArgs& args)
: m_args(args)
{
}
void LinkerSearchPaths::LoadSearchPath(ISearchPath& searchPath) const
{
if (m_args.m_verbose)
{
std::cout << std::format("Loading search path: \"{}\"\n", searchPath.GetPath());
}
ObjLoading::LoadIWDsInSearchPath(searchPath);
}
void LinkerSearchPaths::UnloadSearchPath(ISearchPath& searchPath) const
{
if (m_args.m_verbose)
{
std::cout << std::format("Unloading search path: \"{}\"\n", searchPath.GetPath());
}
ObjLoading::UnloadIWDsInSearchPath(searchPath);
}
SearchPaths LinkerSearchPaths::GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName)
{
SearchPaths searchPathsForProject;
for (const auto& searchPathStr : m_args.GetAssetSearchPathsForProject(gameName, projectName))
{
auto absolutePath = fs::absolute(searchPathStr);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Adding asset search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding asset search path: {}\n", absolutePath.string());
auto searchPath = std::make_unique<SearchPathFilesystem>(searchPathStr);
LoadSearchPath(*searchPath);
searchPathsForProject.IncludeSearchPath(searchPath.get());
m_loaded_project_search_paths.emplace_back(std::move(searchPath));
}
searchPathsForProject.IncludeSearchPath(&m_asset_search_paths);
for (auto* iwd : IWD::Repository)
{
searchPathsForProject.IncludeSearchPath(iwd);
}
return searchPathsForProject;
}
SearchPaths LinkerSearchPaths::GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName)
{
SearchPaths searchPathsForProject;
for (const auto& searchPathStr : m_args.GetGdtSearchPathsForProject(gameName, projectName))
{
auto absolutePath = fs::absolute(searchPathStr);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Adding gdt search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding gdt search path: {}\n", absolutePath.string());
searchPathsForProject.CommitSearchPath(std::make_unique<SearchPathFilesystem>(searchPathStr));
}
searchPathsForProject.IncludeSearchPath(&m_gdt_search_paths);
return searchPathsForProject;
}
SearchPaths LinkerSearchPaths::GetSourceSearchPathsForProject(const std::string& projectName)
{
SearchPaths searchPathsForProject;
for (const auto& searchPathStr : m_args.GetSourceSearchPathsForProject(projectName))
{
auto absolutePath = fs::absolute(searchPathStr);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Adding source search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding source search path: {}\n", absolutePath.string());
searchPathsForProject.CommitSearchPath(std::make_unique<SearchPathFilesystem>(searchPathStr));
}
searchPathsForProject.IncludeSearchPath(&m_source_search_paths);
return searchPathsForProject;
}
bool LinkerSearchPaths::BuildProjectIndependentSearchPaths()
{
for (const auto& path : m_args.GetProjectIndependentAssetSearchPaths())
{
auto absolutePath = fs::absolute(path);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Adding asset search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding asset search path: {}\n", absolutePath.string());
auto searchPath = std::make_unique<SearchPathFilesystem>(absolutePath.string());
LoadSearchPath(*searchPath);
m_asset_search_paths.CommitSearchPath(std::move(searchPath));
}
for (const auto& path : m_args.GetProjectIndependentGdtSearchPaths())
{
auto absolutePath = fs::absolute(path);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Loading gdt search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding gdt search path: {}\n", absolutePath.string());
m_gdt_search_paths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(absolutePath.string()));
}
for (const auto& path : m_args.GetProjectIndependentSourceSearchPaths())
{
auto absolutePath = fs::absolute(path);
if (!fs::is_directory(absolutePath))
{
if (m_args.m_verbose)
std::cout << std::format("Loading source search path (Not found): {}\n", absolutePath.string());
continue;
}
if (m_args.m_verbose)
std::cout << std::format("Adding source search path: {}\n", absolutePath.string());
m_source_search_paths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(absolutePath.string()));
}
return true;
}
void LinkerSearchPaths::UnloadProjectSpecificSearchPaths()
{
for (const auto& loadedSearchPath : m_loaded_project_search_paths)
{
UnloadSearchPath(*loadedSearchPath);
}
m_loaded_project_search_paths.clear();
}

View File

@ -1,45 +0,0 @@
#pragma once
#include "LinkerArgs.h"
#include "SearchPath/SearchPaths.h"
#include <memory>
#include <vector>
class LinkerSearchPaths
{
public:
explicit LinkerSearchPaths(const LinkerArgs& args);
/**
* \brief Loads a search path.
* \param searchPath The search path to load.
*/
void LoadSearchPath(ISearchPath& searchPath) const;
/**
* \brief Unloads a search path.
* \param searchPath The search path to unload.
*/
void UnloadSearchPath(ISearchPath& searchPath) const;
SearchPaths GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName);
SearchPaths GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName);
SearchPaths GetSourceSearchPathsForProject(const std::string& projectName);
/**
* \brief Initializes the Linker object's search paths based on the user's input.
* \return \c true if building the search paths was successful, otherwise \c false.
*/
bool BuildProjectIndependentSearchPaths();
void UnloadProjectSpecificSearchPaths();
private:
const LinkerArgs& m_args;
std::vector<std::unique_ptr<ISearchPath>> m_loaded_project_search_paths;
SearchPaths m_asset_search_paths;
SearchPaths m_gdt_search_paths;
SearchPaths m_source_search_paths;
};