diff --git a/docs/example/ExtendZone/zone_source/ExtendZoneProject.zone b/docs/example/ExtendZone/zone_source/ExtendZoneProject.zone index 7a10900d2..7a02c8fb4 100644 --- a/docs/example/ExtendZone/zone_source/ExtendZoneProject.zone +++ b/docs/example/ExtendZone/zone_source/ExtendZoneProject.zone @@ -4,6 +4,9 @@ // Overwrite the name of the zone to be "ui" >name,ui +// Set type to fastfile +>type,fastfile + // Add custom assets material,,clanlvl_box material,,xp diff --git a/docs/example/IPakZone/build.sh b/docs/example/IPakZone/build.sh new file mode 100644 index 000000000..c7fafd221 --- /dev/null +++ b/docs/example/IPakZone/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# You can now either build the fastfile by building the project itself +Linker IPakZone + +# ...or by building both targets separately +Linker "IPakZone/IPakZone_ff" "IPakZone/IPakZone_ipak" \ No newline at end of file diff --git a/docs/example/IPakZone/images/sample_image.iwi b/docs/example/IPakZone/images/sample_image.iwi new file mode 100644 index 000000000..e69de29bb diff --git a/docs/example/IPakZone/zone_source/IPakZone.zone b/docs/example/IPakZone/zone_source/IPakZone.zone new file mode 100644 index 000000000..21f50b40a --- /dev/null +++ b/docs/example/IPakZone/zone_source/IPakZone.zone @@ -0,0 +1,8 @@ +// Set the game to "Call Of Duty: Black Ops 2" +>game,T6 + +// Set type to none, only makes the other two projects build +>type,none + +build,IPakZone_ff +build,IPakZone_ipak diff --git a/docs/example/IPakZone/zone_source/IPakZone_ff.zone b/docs/example/IPakZone/zone_source/IPakZone_ff.zone new file mode 100644 index 000000000..42f9fb2cf --- /dev/null +++ b/docs/example/IPakZone/zone_source/IPakZone_ff.zone @@ -0,0 +1,11 @@ +// Set the game to "Call Of Duty: Black Ops 2" +>game,T6 + +// Overwrite the name of the fastfile to be "IPakZone" +>name,IPakZone + +// Set type to fastfile +>type,fastfile + +// Add custom assets +image,sample_image diff --git a/docs/example/IPakZone/zone_source/IPakZone_ipak.zone b/docs/example/IPakZone/zone_source/IPakZone_ipak.zone new file mode 100644 index 000000000..a7b8e0c42 --- /dev/null +++ b/docs/example/IPakZone/zone_source/IPakZone_ipak.zone @@ -0,0 +1,11 @@ +// Set the game to "Call Of Duty: Black Ops 2" +>game,T6 + +// Overwrite the name of the ipak to be "IPakZone" +>name,IPakZone + +// Set type to ipak +>type,ipak + +// Add custom assets +image,sample_image diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp index d626364c4..1f7df57ed 100644 --- a/src/Linker/Linker.cpp +++ b/src/Linker/Linker.cpp @@ -44,6 +44,7 @@ const IZoneCreator* const ZONE_CREATORS[] enum class ProjectType { + NONE, FASTFILE, IPAK, @@ -52,6 +53,7 @@ enum class ProjectType constexpr const char* PROJECT_TYPE_NAMES[static_cast(ProjectType::MAX)] { + "none", "fastfile", "ipak" }; @@ -168,7 +170,7 @@ class LinkerImpl final : public Linker return true; } - static bool GetNameFromZoneDefinition(std::string& name, const std::string& projectName, const ZoneDefinition& zoneDefinition) + static bool GetNameFromZoneDefinition(std::string& name, const std::string& targetName, const ZoneDefinition& zoneDefinition) { auto firstNameEntry = true; const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_NAME); @@ -183,27 +185,27 @@ class LinkerImpl final : public Linker { if (name != i->second->m_value) { - std::cout << "Conflicting names in project \"" << projectName << "\": " << name << " != " << i->second << std::endl; + std::cout << "Conflicting names in target \"" << targetName << "\": " << name << " != " << i->second << std::endl; return false; } } } if (firstNameEntry) - name = projectName; + name = targetName; return true; } - std::unique_ptr ReadZoneDefinition(const std::string& projectName, ISearchPath* sourceSearchPath) const + std::unique_ptr ReadZoneDefinition(const std::string& targetName, ISearchPath* sourceSearchPath) const { std::unique_ptr zoneDefinition; { - const auto definitionFileName = projectName + ".zone"; + const auto definitionFileName = targetName + ".zone"; const auto definitionStream = sourceSearchPath->Open(definitionFileName); if (!definitionStream.IsOpen()) { - std::cout << "Could not find zone definition file for project \"" << projectName << "\"." << std::endl; + std::cout << "Could not find zone definition file for target \"" << targetName << "\"." << std::endl; return nullptr; } @@ -213,14 +215,14 @@ class LinkerImpl final : public Linker if (!zoneDefinition) { - std::cout << "Failed to read zone definition file for project \"" << projectName << "\"." << std::endl; + std::cout << "Failed to read zone definition file for target \"" << targetName << "\"." << std::endl; return nullptr; } - if (!GetNameFromZoneDefinition(zoneDefinition->m_name, projectName, *zoneDefinition)) + if (!GetNameFromZoneDefinition(zoneDefinition->m_name, targetName, *zoneDefinition)) return nullptr; - if (!IncludeAdditionalZoneDefinitions(projectName, *zoneDefinition, sourceSearchPath)) + if (!IncludeAdditionalZoneDefinitions(targetName, *zoneDefinition, sourceSearchPath)) return nullptr; if (!IncludeAssetLists(*zoneDefinition, sourceSearchPath)) @@ -229,7 +231,7 @@ class LinkerImpl final : public Linker return zoneDefinition; } - bool ProcessZoneDefinitionIgnores(const std::string& projectName, ZoneCreationContext& context, ISearchPath* sourceSearchPath) const + bool ProcessZoneDefinitionIgnores(const std::string& targetName, ZoneCreationContext& context, ISearchPath* sourceSearchPath) const { if (context.m_definition->m_ignores.empty()) return true; @@ -242,7 +244,7 @@ class LinkerImpl final : public Linker for (const auto& ignore : context.m_definition->m_ignores) { - if (ignore == projectName) + if (ignore == targetName) continue; std::vector assetList; @@ -269,9 +271,9 @@ class LinkerImpl final : public Linker return false; } - static bool GetProjectTypeFromZoneDefinition(ProjectType& projectType, const std::string& projectName, const ZoneDefinition& zoneDefinition) + static bool GetProjectTypeFromZoneDefinition(ProjectType& projectType, const std::string& targetName, const ZoneDefinition& zoneDefinition) { - auto firstGameEntry = true; + auto firstTypeEntry = true; const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_TYPE); for (auto i = rangeBegin; i != rangeEnd; ++i) { @@ -282,29 +284,34 @@ class LinkerImpl final : public Linker return false; } - if (firstGameEntry) + if (firstTypeEntry) { projectType = parsedProjectType; - firstGameEntry = false; + firstTypeEntry = false; } else { if (projectType != parsedProjectType) { - std::cerr << "Conflicting types in project \"" << projectName << "\": " << PROJECT_TYPE_NAMES[static_cast(projectType)] + std::cerr << "Conflicting types in target \"" << targetName << "\": " << PROJECT_TYPE_NAMES[static_cast(projectType)] << " != " << PROJECT_TYPE_NAMES[static_cast(parsedProjectType)] << std::endl; return false; } } } - if (firstGameEntry) - projectType = ProjectType::FASTFILE; + if (firstTypeEntry) + { + if (zoneDefinition.m_assets.empty()) + projectType = ProjectType::NONE; + else + projectType = ProjectType::FASTFILE; + } return true; } - static bool GetGameNameFromZoneDefinition(std::string& gameName, const std::string& projectName, const ZoneDefinition& zoneDefinition) + static bool GetGameNameFromZoneDefinition(std::string& gameName, const std::string& targetName, const ZoneDefinition& zoneDefinition) { auto firstGameEntry = true; const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_GAME); @@ -319,7 +326,7 @@ class LinkerImpl final : public Linker { if (gameName != i->second->m_value) { - std::cout << "Conflicting game names in project \"" << projectName << "\": " << gameName << " != " << i->second << std::endl; + std::cout << "Conflicting game names in target \"" << targetName << "\": " << gameName << " != " << i->second << std::endl; return false; } } @@ -327,7 +334,7 @@ class LinkerImpl final : public Linker if (firstGameEntry) { - std::cout << "No game name was specified for project \"" << projectName << "\"" << std::endl; + std::cout << "No game name was specified for target \"" << targetName << "\"" << std::endl; return false; } @@ -360,13 +367,13 @@ class LinkerImpl final : public Linker return true; } - std::unique_ptr CreateZoneForDefinition(const std::string& projectName, ZoneDefinition& zoneDefinition, ISearchPath* assetSearchPath, ISearchPath* gdtSearchPath, + std::unique_ptr CreateZoneForDefinition(const std::string& targetName, ZoneDefinition& zoneDefinition, ISearchPath* assetSearchPath, ISearchPath* gdtSearchPath, ISearchPath* sourceSearchPath) const { const auto context = std::make_unique(assetSearchPath, &zoneDefinition); - if (!ProcessZoneDefinitionIgnores(projectName, *context, sourceSearchPath)) + if (!ProcessZoneDefinitionIgnores(targetName, *context, sourceSearchPath)) return nullptr; - if (!GetGameNameFromZoneDefinition(context->m_game_name, projectName, zoneDefinition)) + if (!GetGameNameFromZoneDefinition(context->m_game_name, targetName, zoneDefinition)) return nullptr; if (!LoadGdtFilesFromZoneDefinition(context->m_gdt_files, zoneDefinition, gdtSearchPath)) return nullptr; @@ -405,9 +412,10 @@ class LinkerImpl final : public Linker return true; } - bool BuildFastFile(const std::string& projectName, ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths, SearchPaths& gdtSearchPaths, SearchPaths& sourceSearchPaths) const + bool BuildFastFile(const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths, SearchPaths& gdtSearchPaths, + SearchPaths& sourceSearchPaths) const { - const auto zone = CreateZoneForDefinition(projectName, zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths); + const auto zone = CreateZoneForDefinition(targetName, zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths); auto result = zone != nullptr; if (zone) result = WriteZoneToFile(projectName, zone.get()); @@ -415,7 +423,7 @@ class LinkerImpl final : public Linker return result; } - bool BuildIPak(const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths, SearchPaths& sourceSearchPaths) const + bool BuildIPak(const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths) const { const fs::path ipakFolderPath(m_args.GetOutputFolderPathForProject(projectName)); auto ipakFilePath(ipakFolderPath); @@ -450,44 +458,65 @@ class LinkerImpl final : public Linker return true; } - bool BuildProject(const std::string& projectName) + bool BuildReferencedTargets(const std::string& projectName, const std::string& targetName, const ZoneDefinition& zoneDefinition) + { + return std::all_of(zoneDefinition.m_targets_to_build.begin(), zoneDefinition.m_targets_to_build.end(), [this, &projectName, &targetName](const std::string& buildTargetName) + { + if (buildTargetName == targetName) + { + std::cerr << "Cannot build target with same name: \"" << targetName << "\"\n"; + return false; + } + + std::cout << "Building referenced target \"" << buildTargetName << "\"\n"; + return BuildProject(projectName, buildTargetName); + }); + } + + bool BuildProject(const std::string& projectName, const std::string& targetName) { auto sourceSearchPaths = m_search_paths.GetSourceSearchPathsForProject(projectName); - const auto zoneDefinition = ReadZoneDefinition(projectName, &sourceSearchPaths); + const auto zoneDefinition = ReadZoneDefinition(targetName, &sourceSearchPaths); if (!zoneDefinition) return false; ProjectType projectType; - if (!GetProjectTypeFromZoneDefinition(projectType, projectName, *zoneDefinition)) + if (!GetProjectTypeFromZoneDefinition(projectType, targetName, *zoneDefinition)) return false; - std::string gameName; - if (!GetGameNameFromZoneDefinition(gameName, projectName, *zoneDefinition)) - return false; - utils::MakeStringLowerCase(gameName); + auto result = true; + if (projectType != ProjectType::NONE) + { + std::string gameName; + if (!GetGameNameFromZoneDefinition(gameName, targetName, *zoneDefinition)) + return false; + utils::MakeStringLowerCase(gameName); - auto assetSearchPaths = m_search_paths.GetAssetSearchPathsForProject(gameName, projectName); - auto gdtSearchPaths = m_search_paths.GetGdtSearchPathsForProject(gameName, projectName); + auto assetSearchPaths = m_search_paths.GetAssetSearchPathsForProject(gameName, projectName); + auto gdtSearchPaths = m_search_paths.GetGdtSearchPathsForProject(gameName, projectName); - auto result = false; - switch (projectType) - { - case ProjectType::FASTFILE: - result = BuildFastFile(projectName, *zoneDefinition, assetSearchPaths, gdtSearchPaths, sourceSearchPaths); - break; + switch (projectType) + { + case ProjectType::FASTFILE: + result = BuildFastFile(projectName, targetName, *zoneDefinition, assetSearchPaths, gdtSearchPaths, sourceSearchPaths); + break; - case ProjectType::IPAK: - result = BuildIPak(projectName, *zoneDefinition, assetSearchPaths, sourceSearchPaths); - break; + case ProjectType::IPAK: + result = BuildIPak(projectName, *zoneDefinition, assetSearchPaths); + break; - default: - assert(false); - break; + default: + assert(false); + result = false; + break; + } } m_search_paths.UnloadProjectSpecificSearchPaths(); + result = result && BuildReferencedTargets(projectName, targetName, *zoneDefinition); + return result; } @@ -536,6 +565,40 @@ class LinkerImpl final : public Linker m_loaded_zones.clear(); } + static bool GetProjectAndTargetFromProjectSpecifier(const std::string& projectSpecifier, std::string& projectName, std::string& targetName) + { + const auto targetNameSeparatorIndex = projectSpecifier.find_first_of('/'); + if (targetNameSeparatorIndex == std::string::npos) + { + projectName = projectSpecifier; + targetName = projectSpecifier; + } + else if (projectSpecifier.find_first_of('/', targetNameSeparatorIndex + 1) != std::string::npos) + { + std::cerr << "Project specifier cannot have more than one target name: \"" << projectSpecifier << "\"\n"; + return false; + } + else + { + projectName = projectSpecifier.substr(0, targetNameSeparatorIndex); + targetName = projectSpecifier.substr(targetNameSeparatorIndex + 1); + } + + if (projectName.empty()) + { + std::cerr << "Project name cannot be empty: \"" << projectSpecifier << "\"\n"; + return false; + } + + if (targetName.empty()) + { + std::cerr << "Target name cannot be empty: \"" << projectSpecifier << "\"\n"; + return false; + } + + return true; + } + public: LinkerImpl() : m_search_paths(m_args) @@ -554,9 +617,17 @@ class LinkerImpl final : public Linker return false; auto result = true; - for (const auto& projectName : m_args.m_projects_to_build) + for (const auto& projectSpecifier : m_args.m_project_specifiers_to_build) { - if (!BuildProject(projectName)) + std::string projectName; + std::string targetName; + if (!GetProjectAndTargetFromProjectSpecifier(projectSpecifier, projectName, targetName)) + { + result = false; + break; + } + + if (!BuildProject(projectName, targetName)) { result = false; break; diff --git a/src/Linker/LinkerArgs.cpp b/src/Linker/LinkerArgs.cpp index 83f52e8c1..9d19383f5 100644 --- a/src/Linker/LinkerArgs.cpp +++ b/src/Linker/LinkerArgs.cpp @@ -210,8 +210,8 @@ bool LinkerArgs::ParseArgs(const int argc, const char** argv) return false; } - m_projects_to_build = m_argument_parser.GetArguments(); - if (m_projects_to_build.empty()) + m_project_specifiers_to_build = m_argument_parser.GetArguments(); + if (m_project_specifiers_to_build.empty()) { // No projects to build specified... PrintUsage(); diff --git a/src/Linker/LinkerArgs.h b/src/Linker/LinkerArgs.h index 6247724ee..dfaae2b46 100644 --- a/src/Linker/LinkerArgs.h +++ b/src/Linker/LinkerArgs.h @@ -41,7 +41,7 @@ class LinkerArgs public: std::vector m_zones_to_load; - std::vector m_projects_to_build; + std::vector m_project_specifiers_to_build; std::string m_base_folder; std::string m_out_folder; diff --git a/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.cpp b/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.cpp new file mode 100644 index 000000000..1c4a1661a --- /dev/null +++ b/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.cpp @@ -0,0 +1,19 @@ +#include "SequenceZoneDefinitionBuild.h" + +#include "Parsing/ZoneDefinition/Matcher/ZoneDefinitionMatcherFactory.h" + +SequenceZoneDefinitionBuild::SequenceZoneDefinitionBuild() +{ + const ZoneDefinitionMatcherFactory create(this); + + AddMatchers({ + create.Keyword("build"), + create.Char(','), + create.Field().Capture(CAPTURE_BUILD_TARGET_NAME) + }); +} + +void SequenceZoneDefinitionBuild::ProcessMatch(ZoneDefinition* state, SequenceResult& result) const +{ + state->m_targets_to_build.emplace_back(result.NextCapture(CAPTURE_BUILD_TARGET_NAME).FieldValue()); +} diff --git a/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.h b/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.h new file mode 100644 index 000000000..8e5b963b0 --- /dev/null +++ b/src/ZoneCommon/Parsing/ZoneDefinition/Sequence/SequenceZoneDefinitionBuild.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Parsing/ZoneDefinition/ZoneDefinitionParser.h" + +class SequenceZoneDefinitionBuild final : public ZoneDefinitionParser::sequence_t +{ + static constexpr auto CAPTURE_BUILD_TARGET_NAME = 1; + +protected: + void ProcessMatch(ZoneDefinition* state, SequenceResult& result) const override; + +public: + SequenceZoneDefinitionBuild(); +}; diff --git a/src/ZoneCommon/Parsing/ZoneDefinition/ZoneDefinitionParser.cpp b/src/ZoneCommon/Parsing/ZoneDefinition/ZoneDefinitionParser.cpp index a6ae9552c..fcb7c0846 100644 --- a/src/ZoneCommon/Parsing/ZoneDefinition/ZoneDefinitionParser.cpp +++ b/src/ZoneCommon/Parsing/ZoneDefinition/ZoneDefinitionParser.cpp @@ -1,6 +1,7 @@ #include "ZoneDefinitionParser.h" #include "Sequence/SequenceZoneDefinitionAssetList.h" +#include "Sequence/SequenceZoneDefinitionBuild.h" #include "Sequence/SequenceZoneDefinitionEntry.h" #include "Sequence/SequenceZoneDefinitionIgnore.h" #include "Sequence/SequenceZoneDefinitionInclude.h" @@ -18,6 +19,7 @@ const std::vector::seq new SequenceZoneDefinitionInclude(), new SequenceZoneDefinitionIgnore(), new SequenceZoneDefinitionAssetList(), + new SequenceZoneDefinitionBuild(), new SequenceZoneDefinitionEntry() }); diff --git a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h index 4ec8a915f..74c540121 100644 --- a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h +++ b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h @@ -37,6 +37,7 @@ class ZoneDefinition std::vector m_includes; std::vector m_asset_lists; std::vector m_ignores; + std::vector m_targets_to_build; std::vector m_assets; void AddMetaData(std::string key, std::string value);