#include "GacGen.h" using namespace vl::collections; using namespace vl::filesystem; using namespace vl::presentation::controls; GuiResourceCpuArchitecture targetCpuArchitecture = GuiResourceCpuArchitecture::Unspecified; Array* arguments = 0; WString executablePath; void SetTargetCpuArchitecture() { if (arguments->Count() > 0) { if (arguments->Get(0) == L"/P32" || arguments->Get(0) == L"/D32" || arguments->Get(0) == L"/C32") { targetCpuArchitecture = GuiResourceCpuArchitecture::x86; } else if (arguments->Get(0) == L"/P64" || arguments->Get(0) == L"/D64" || arguments->Get(0) == L"/C64") { targetCpuArchitecture = GuiResourceCpuArchitecture::x64; } } } #if defined VCZH_MSVC int wmain(int argc, wchar_t* argv[]) { executablePath = argv[0]; Array _arguments(argc - 1); for (vint i = 1; i < argc; i++) { _arguments[i - 1] = argv[i]; } arguments = &_arguments; SetTargetCpuArchitecture(); return SetupGacGenNativeController(); } #elif defined VCZH_GCC int main(int argc, char* argv[]) { executablePath = atow(argv[0]); Array _arguments(argc - 1); for (vint i = 1; i < argc; i++) { _arguments[i - 1] = atow(argv[i]); } arguments = &_arguments; SetTargetCpuArchitecture(); return SetupGacGenNativeController(); } #endif void PrintErrors(List& errors) { List output; GuiResourceError::SortAndLog(errors, output); for (auto line : output) { PrintErrorMessage(line); } } void SaveErrors(FilePath errorFilePath, List& errors) { PrintErrors(errors); if (WriteErrors(errors, errorFilePath)) { PrintErrorMessage(L"error> Error information is saved at : " + errorFilePath.GetFullPath()); } else { PrintErrorMessage(L"error> Unable to write : " + errorFilePath.GetFullPath()); } } class Callback : public Object, public IGuiResourcePrecompileCallback, public IWfCompilerCallback { public: vint lastPass = -1; void OnLoadEnvironment()override { PrintInformationMessage(L" Workflow: Loading metadata from registered types ..."); } void OnInitialize(analyzer::WfLexicalScopeManager* manager)override { PrintInformationMessage(L" Workflow: Creating metadata from declarations ..."); } void OnValidateModule(Ptr module)override { PrintInformationMessage(L" Workflow: Validating module " + module->name.value + L" ..."); } void OnGenerateMetadata()override { PrintInformationMessage(L" Workflow: Generating metadata ..."); } void OnGenerateCode(Ptr module)override { PrintInformationMessage(L" Workflow: Generating code for module " + module->name.value + L" ..."); } void OnGenerateDebugInfo()override { PrintInformationMessage(L" Workflow: Generating debug information ..."); } IWfCompilerCallback* GetCompilerCallback()override { return this; } void PrintPass(vint passIndex) { if (lastPass != passIndex) { lastPass = passIndex; switch (passIndex) { case IGuiResourceTypeResolver_Precompile::Workflow_Collect: PrintInformationMessage(L"Pass: (1/8) Collect workflow scripts"); break; case IGuiResourceTypeResolver_Precompile::Workflow_Compile: PrintInformationMessage(L"Pass: (2/8) Compile view model scripts"); break; case IGuiResourceTypeResolver_Precompile::Instance_CollectInstanceTypes: PrintInformationMessage(L"Pass: (3/8) Collect instances"); break; case IGuiResourceTypeResolver_Precompile::Instance_CompileInstanceTypes: PrintInformationMessage(L"Pass: (4/8) Validate instances"); break; case IGuiResourceTypeResolver_Precompile::Instance_CollectEventHandlers: PrintInformationMessage(L"Pass: (5/8) Generate instance type stubs"); break; case IGuiResourceTypeResolver_Precompile::Instance_CompileEventHandlers: PrintInformationMessage(L"Pass: (6/8) Compile instance type stubs"); break; case IGuiResourceTypeResolver_Precompile::Instance_GenerateInstanceClass: PrintInformationMessage(L"Pass: (7/8) Generate instance types"); break; case IGuiResourceTypeResolver_Precompile::Instance_CompileInstanceClass: PrintInformationMessage(L"Pass: (8/8) Compile instance types"); break; } } } void OnPerPass(vint passIndex)override { PrintPass(passIndex); } void OnPerResource(vint passIndex, Ptr resource)override { PrintPass(passIndex); PrintInformationMessage(L" " + resource->GetResourcePath()); } }; struct LoadConfigResult { Ptr resource; Ptr config; }; LoadConfigResult LoadConfig(FilePath inputPath) { List errors; auto resource = GuiResource::LoadFromXml(inputPath.GetFullPath(), errors); for (vint i = errors.Count() - 1; i >= 0; i--) { auto error = errors[i]; if (INVLOC.FindFirst(error.message, L"Path of imported folder does not exist:", Locale::None).key == 0) { errors.RemoveAt(i); } } if (errors.Count() > 0) { PrintErrorMessage(L"error> Failed to load resource."); PrintErrors(errors); return {}; } auto config = CodegenConfig::LoadConfig(resource, errors); if (!config) { PrintErrorMessage(L"error> Failed to load config."); return {}; } if (errors.Count() > 0) { PrintErrorMessage(L"error> Failed to load resource metadata."); PrintErrors(errors); return {}; } LoadConfigResult result; result.resource = resource; result.config = config; return result; } bool LoadDependencies(Ptr config, Dictionary& resourceMappings, const WString& logFolderPostfix, FilePath errorFilePath) { if (config->metadata->version == L"") { config->metadata->version = GuiResource::CurrentVersionString; } if (config->metadata->version != GuiResource::CurrentVersionString) { PrintErrorMessage(L"error> Version of the resource should be \"" + WString(GuiResource::CurrentVersionString) + L"\""); return false; } if (config->metadata->dependencies.Count() > 0 && config->metadata->name == L"") { PrintErrorMessage(L"error> Name of the resource should not be empty if it has dependencies."); return false; } SortedList requireds, loaded; CopyFrom(requireds, config->metadata->dependencies); while (requireds.Count() > 0) { auto required = requireds[requireds.Count() - 1]; requireds.RemoveAt(requireds.Count() - 1); if (loaded.Contains(required)) continue; vint index = resourceMappings.Keys().IndexOf(required); if (index == -1) { PrintErrorMessage(L"error> Cannot find the resource file of name: " + required); return false; } FilePath resourceBinary = FilePath(resourceMappings.Values()[index].GetFullPath() + logFolderPostfix) / L"ScriptedResource.bin"; MemoryStream resourceStream; { FileStream fileStream(resourceBinary.GetFullPath(), FileStream::ReadOnly); if (!fileStream.IsAvailable()) { PrintErrorMessage(L"error> Cannot find binary resource file, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); return false; } CopyStream(fileStream, resourceStream); } loaded.Add(required); { resourceStream.SeekFromBegin(0); stream::internal::ContextFreeReader reader(resourceStream); WString metadataText; reader << metadataText; GuiResourceError::List errors; auto parser = GetParserManager()->GetParser(L"XML"); auto xmlMetadata = parser->Parse({}, metadataText, errors); if (!xmlMetadata || errors.Count() > 0) { PrintErrorMessage(L"error> The binary resource file has an invalid metadata, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); return false; } auto metadata = Ptr(new GuiResourceMetadata); metadata->LoadFromXml(xmlMetadata, {}, errors); if (errors.Count() > 0) { PrintErrorMessage(L"error> The binary resource file has an invalid metadata, please make sure the required resource \"" + required + L"\" has been properly compiled: " + required); return false; } CopyFrom(requireds, metadata->dependencies, true); } resourceStream.SeekFromBegin(0); GuiResourceError::List errors; GetResourceManager()->LoadResourceOrPending(resourceStream, errors, GuiResourceUsage::InstanceClass); if (errors.Count() > 0) { SaveErrors(errorFilePath, errors); return false; } } List pendings; GetResourceManager()->GetPendingResourceNames(pendings); if (pendings.Count() > 0) { PrintErrorMessage(L"error> Unable to find dependencies: " + From(pendings) .Aggregate(WString::Empty, [](const WString& a, const WString& b) {return a == L"" ? b : a + L", " + b; }) ); return false; } return true; } void CompileResource(bool partialMode, FilePath inputPath, Nullable mappingPath) { PrintSuccessMessage(L"gacgen> Clearning logs ... : " + inputPath.GetFullPath()); WString logFolderPostfix; switch (targetCpuArchitecture) { case GuiResourceCpuArchitecture::x86: logFolderPostfix = L".log/x32"; break; case GuiResourceCpuArchitecture::x64: logFolderPostfix = L".log/x64"; break; default:; } FilePath logFolderPath = inputPath.GetFullPath() + logFolderPostfix; FilePath scriptFilePath = logFolderPath / L"Workflow.txt"; FilePath errorFilePath = logFolderPath / L"Errors.txt"; FilePath workingDir = inputPath.GetFolder(); { auto loadConfigResult = LoadConfig(inputPath); if (!loadConfigResult.resource) return; Dictionary resourceMappings; if (mappingPath) { FileStream fileStream(mappingPath.Value().GetFullPath(), FileStream::ReadOnly); if (!fileStream.IsAvailable()) { PrintErrorMessage(L"error> Failed to load mapping file: " + mappingPath.Value().GetFullPath()); return; } BomDecoder decoder; DecoderStream decoderStream(fileStream, decoder); StreamReader reader(decoderStream); while (!reader.IsEnd()) { auto line = reader.ReadLine(); if (line != L"") { auto arrow = INVLOC.FindFirst(line, L"=>", Locale::None); if (arrow.key == -1) { PrintErrorMessage(L"warning> Unable to parse mapping information: " + line); return; } auto name = line.Left(arrow.key); auto value = line.Right(line.Length() - arrow.key - arrow.value); if (resourceMappings.Keys().Contains(name)) { PrintErrorMessage(L"warning> Find duplicate mapping information: " + line); return; } resourceMappings.Add(name, value); } } } if (!LoadDependencies(loadConfigResult.config, resourceMappings, logFolderPostfix, errorFilePath)) { return; } } if (partialMode) { PrintInformationMessage(L"gacgen> Partial mode activated, all output files will be put under " + logFolderPath.GetFullPath()); } { Folder logFolder(logFolderPath); if (logFolder.Exists()) { if (!logFolder.Delete(true)) { PrintErrorMessage(L"gacgen> Unable to delete file in the log folder : " + logFolderPath.GetFullPath()); return; } } if (!logFolder.Create(true)) { Thread::Sleep(500); if (!logFolder.Create(true)) { PrintErrorMessage(L"gacgen> Unable to create log folder : " + logFolderPath.GetFullPath()); return; } } } Ptr resource; { PrintSuccessMessage(L"gacgen> Making : " + inputPath.GetFullPath()); List errors; resource = GuiResource::LoadFromXml(inputPath.GetFullPath(), errors); if (errors.Count() > 0) { PrintErrorMessage(L"error> Failed to load resource."); SaveErrors(errorFilePath, errors); return; } } List cppResourcePaths; List cppCompressedPaths; List resResourcePaths; List resCompressedPaths; List resAssemblyPaths; Ptr cppOutput; Ptr resOutput; { Ptr config; { List errors; config = CodegenConfig::LoadConfig(resource, errors); if (!config) { PrintErrorMessage(L"error> Failed to load config."); return; } if (errors.Count() > 0) { PrintErrorMessage(L"error> Failed to load resource metadata."); SaveErrors(errorFilePath, errors); return; } } { cppResourcePaths.Add(logFolderPath / L"Resource.bin"); cppCompressedPaths.Add(logFolderPath / L"Compressed.bin"); resResourcePaths.Add(logFolderPath / L"ScriptedResource.bin"); resCompressedPaths.Add(logFolderPath / L"ScriptedCompressed.bin"); resAssemblyPaths.Add(logFolderPath / L"Assembly.bin"); } cppOutput = config->cppOutput; switch (targetCpuArchitecture) { case GuiResourceCpuArchitecture::x86: resOutput = config->resOutputx32; break; case GuiResourceCpuArchitecture::x64: resOutput = config->resOutputx64; break; default:; } if (cppOutput) { if (config->cppOutput->resource != L"") cppResourcePaths.Add(workingDir / config->cppOutput->resource); if (config->cppOutput->compressed != L"") resAssemblyPaths.Add(workingDir / config->cppOutput->compressed); } if (resOutput) { if (resOutput->resource != L"") resResourcePaths.Add(workingDir / resOutput->resource); if (resOutput->compressed != L"") resCompressedPaths.Add(workingDir / resOutput->compressed); if (resOutput->assembly != L"") resAssemblyPaths.Add(workingDir / resOutput->assembly); } } PrintSuccessMessage(L"gacgen> Compiling..."); List errors; Callback callback; auto precompiledFolder = PrecompileResource(resource, targetCpuArchitecture, &callback, errors); if (errors.Count() > 0) { SaveErrors(errorFilePath, errors); return; } if (auto compiled = WriteWorkflowScript(precompiledFolder, L"Workflow/InstanceClass", scriptFilePath)) { if (cppOutput) { PrintSuccessMessage(L"gacgen> Generating C++ source code ..."); auto input = Ptr(new WfCppInput(cppOutput->name)); input->multiFile = WfCppFileSwitch::Enabled; input->reflection = WfCppFileSwitch::Enabled; input->comment = L"GacGen.exe " + FilePath(inputPath).GetName(); input->defaultFileName = cppOutput->name + L"PartialClasses"; input->includeFileName = cppOutput->name; CopyFrom(input->normalIncludes, cppOutput->normalIncludes); CopyFrom(input->reflectionIncludes, cppOutput->reflectionIncludes); FilePath cppFolder = workingDir / cppOutput->sourceFolder; if (partialMode) { File(logFolderPath / L"CppOutput.txt").WriteAllText(cppFolder.GetFullPath(), false, BomEncoder::Mbcs); cppFolder = logFolderPath / L"Source"; if (!Folder(cppFolder).Create(true)) { PrintSuccessMessage(L"gacgen> Unable to create source folder : " + cppFolder.GetFullPath()); } } auto output = WriteCppCodesToFile(resource, compiled, input, cppFolder, errors); if (errors.Count() > 0) { SaveErrors(errorFilePath, errors); return; } if (cppOutput->cppResource != L"") { WriteEmbeddedResource(resource, input, output, false, cppFolder / cppOutput->cppResource); } if (cppOutput->cppCompressed != L"") { WriteEmbeddedResource(resource, input, output, true, cppFolder / cppOutput->cppCompressed); } } for (auto filePath : cppResourcePaths) { PrintSuccessMessage(L"Generating binary resource file (no script): " + filePath.GetFullPath()); WriteBinaryResource(resource, false, false, filePath, {}); } for (auto filePath : cppCompressedPaths) { PrintSuccessMessage(L"Generating compressed resource file (no script): " + filePath.GetFullPath()); WriteBinaryResource(resource, true, false, filePath, {}); } for (auto filePath : resResourcePaths) { PrintSuccessMessage(L"Generating binary resource files : " + filePath.GetFullPath()); WriteBinaryResource(resource, false, true, filePath, {}); } for (auto filePath : resCompressedPaths) { PrintSuccessMessage(L"Generating compressed resource files : " + filePath.GetFullPath()); WriteBinaryResource(resource, true, true, filePath, {}); } for (auto filePath : resAssemblyPaths) { PrintSuccessMessage(L"Generating assembly files : " + filePath.GetFullPath()); WriteBinaryResource(resource, false, false, {}, filePath); } if (partialMode) { List lines; List* outputPaths[] = { &cppResourcePaths, &cppCompressedPaths, &resResourcePaths, &resCompressedPaths, &resAssemblyPaths, }; for (vint i = 0; i < sizeof(outputPaths) / sizeof(*outputPaths); i++) { auto& paths = *outputPaths[i]; if (paths.Count() == 2) { lines.Add(L"copy \"" + paths[0].GetFullPath() + L"\" \"" + paths[1].GetFullPath() + L"\""); } } if (lines.Count() > 0) { File(logFolderPath / L"Deploy.bat").WriteAllLines(lines, false, BomEncoder::Mbcs); } } } } void DumpResource(FilePath inputPath, FilePath outputPath) { PrintSuccessMessage(L"gacgen> Dumping : " + inputPath.GetFullPath()); auto loadConfigResult = LoadConfig(inputPath); if (!loadConfigResult.resource) return; auto doc = Ptr(new XmlDocument); auto xmlRoot = Ptr(new XmlElement); xmlRoot->name.value = L"ResourceMetadata"; doc->rootElement = xmlRoot; xmlRoot->subNodes.Add(loadConfigResult.resource->GetMetadata()->SaveToXml()); { SortedList paths; List> folders; folders.Add(loadConfigResult.resource); for (vint i = 0; i < folders.Count(); i++) { auto currentFolder = folders[i]; if (currentFolder->GetFileAbsolutePath() != L"" && !paths.Contains(currentFolder->GetFileAbsolutePath())) { paths.Add(currentFolder->GetFileAbsolutePath()); } for (auto item : currentFolder->GetItems()) { if (item->GetFileAbsolutePath() != L"" && !paths.Contains(item->GetFileAbsolutePath())) { paths.Add(item->GetFileAbsolutePath()); } } for (auto folder : currentFolder->GetFolders()) { folders.Add(folder); } } auto xmlInputs = Ptr(new XmlElement); xmlInputs->name.value = L"Inputs"; xmlRoot->subNodes.Add(xmlInputs); for (auto path : paths) { auto xmlInput = Ptr(new XmlElement); xmlInput->name.value = L"Input"; xmlInputs->subNodes.Add(xmlInput); { auto attr = Ptr(new XmlAttribute); attr->name.value = L"Path"; attr->value.value = path; xmlInput->attributes.Add(attr); } } } { SortedList paths; paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Resource.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Compressed.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"ScriptedResource.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"ScriptedCompressed.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x32" / L"Assembly.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Resource.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Compressed.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"ScriptedResource.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"ScriptedCompressed.bin").GetFullPath()); paths.Add((FilePath(inputPath.GetFullPath() + L".log") / L"x64" / L"Assembly.bin").GetFullPath()); auto xmlOutputs = Ptr(new XmlElement); xmlOutputs->name.value = L"Outputs"; xmlRoot->subNodes.Add(xmlOutputs); for (auto path : paths) { auto xmlOutput = Ptr(new XmlElement); xmlOutput->name.value = L"Output"; xmlOutputs->subNodes.Add(xmlOutput); { auto attr = Ptr(new XmlAttribute); attr->name.value = L"Path"; attr->value.value = path; xmlOutput->attributes.Add(attr); } } } { FileStream fileStream(outputPath.GetFullPath(), FileStream::WriteOnly); if (!fileStream.IsAvailable()) { PrintErrorMessage(L"error> Failed to write file: " + outputPath.GetFullPath()); return; } BomEncoder encoder(BomEncoder::Utf8); EncoderStream encoderStream(fileStream, encoder); StreamWriter writer(encoderStream); XmlPrint(doc, writer); } } class GuiReflectionPlugin : public Object, public IGuiPlugin { public: GUI_PLUGIN_NAME(GacUI_Instance_Reflection) { } void Load(bool controllerUnrelatedPlugins, bool controllerRelatedPlugins)override { FilePath exeFolder = FilePath(executablePath).GetFolder(); FilePath metadataFolder = exeFolder; auto binaryName32 = WString::Unmanaged(L"Reflection32.bin"); auto binaryName64 = WString::Unmanaged(L"Reflection64.bin"); { File metadataOverride = exeFolder / L"Metadata.txt"; if (metadataOverride.Exists()) { List lines; metadataOverride.ReadAllLinesByBom(lines); if (lines.Count() < 3) { Console::WriteLine(L"Metadata.txt should contains 3 lines for , , ."); CHECK_FAIL(L"Metadata.txt should contains 3 lines for , , ."); } metadataFolder = exeFolder / lines[0]; binaryName32 = lines[1]; binaryName64 = lines[2]; } } const wchar_t* BINARY_NAME = nullptr; switch (targetCpuArchitecture) { case GuiResourceCpuArchitecture::x86: BINARY_NAME = binaryName32.Buffer(); break; case GuiResourceCpuArchitecture::x64: BINARY_NAME = binaryName64.Buffer(); break; default:; } FilePath binaryPath = metadataFolder / BINARY_NAME; if (!File(binaryPath).Exists()) { Console::WriteLine(L"Unable to find the GacUI type metadata file at: " + binaryPath.GetFullPath()); CHECK_FAIL(L"Unable to find the GacUI type metadata file!"); } #define INSTALL_SERIALIZABLE_TYPE(TYPE) serializableTypes.Add(TypeInfo::content.typeName, Ptr(new SerializableType)); collections::Dictionary> serializableTypes; REFLECTION_PREDEFINED_SERIALIZABLE_TYPES(INSTALL_SERIALIZABLE_TYPE) INSTALL_SERIALIZABLE_TYPE(Color) INSTALL_SERIALIZABLE_TYPE(GlobalStringKey) INSTALL_SERIALIZABLE_TYPE(DocumentFontSize) FileStream fileStream(binaryPath.GetFullPath(), FileStream::ReadOnly); auto typeLoader = LoadMetaonlyTypes(fileStream, serializableTypes); auto tm = GetGlobalTypeManager(); tm->AddTypeLoader(typeLoader); #undef INSTALL_SERIALIZABLE_TYPE } void Unload(bool controllerUnrelatedPlugins, bool controllerRelatedPlugins)override { } }; GUI_REGISTER_PLUGIN(GuiReflectionPlugin) void GuiMain() { Console::SetTitle(L"Vczh GacUI Resource Code Generator for C++"); if (arguments->Count() > 0) { if (arguments->Get(0) == L"/P32" || arguments->Get(0) == L"/P64") { switch (arguments->Count()) { case 2: CompileResource(true, arguments->Get(1), {}); return; case 3: CompileResource(true, arguments->Get(1), { arguments->Get(2) }); return; } } else if (arguments->Get(0) == L"/D32" || arguments->Get(0) == L"/D64") { switch (arguments->Count()) { case 3: DumpResource(arguments->Get(1), arguments->Get(2)); return; } } else if (arguments->Get(0) == L"/C32" || arguments->Get(0) == L"/C64") { switch (arguments->Count()) { case 2: CompileResource(false, arguments->Get(1), {}); return; } } } PrintErrorMessage(L"Usage"); PrintErrorMessage(L" Compile for x86 or x64 for GacBuild.ps1:"); PrintErrorMessage(L" GacGen.exe /P32 []"); PrintErrorMessage(L" GacGen.exe /P64 []"); PrintErrorMessage(L" Dump for x86 or x64 for GacBuild.ps1:"); PrintErrorMessage(L" GacGen.exe /D32 "); PrintErrorMessage(L" GacGen.exe /D64 "); PrintErrorMessage(L" Compile for x86 or x64 if you only want one architecture:"); PrintErrorMessage(L" GacGen.exe /C32 "); PrintErrorMessage(L" GacGen.exe /C64 "); }