Files
UnrealEngine/Engine/Source/Runtime/VerseCompiler/Private/uLang/SourceProject/SourceFileProject.cpp
2025-05-18 13:04:45 +08:00

770 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "uLang/SourceProject/SourceFileProject.h"
#include "uLang/SourceProject/SourceProjectWriter.h"
#include "uLang/Common/Text/FilePathUtils.h"
#include "uLang/Common/Templates/Storage.h"
#include "uLang/JSON/JSON.h"
#include "uLang/SourceProject/SourceProjectUtils.h"
#include "uLang/SourceProject/VerseVersion.h"
// #SUPPORT_LEGACY_VMODULES
// Temporary switch allowing legacy named vmodule files
// until we have converted all Verse code to new directory-driven module hierarchy
#define VERSE_ALLOW_VMODULE_FILES 1
namespace uLang
{
//====================================================================================
// CSourceFileSnippet implementation
//====================================================================================
TOptional<CUTF8String> CSourceFileSnippet::GetText() const
{
// If a modified version exists, return that
if (_ModifiedText)
{
return *_ModifiedText;
}
// Otherwise fetch current version from disk
CUTF8StringBuilder FileContents;
const bool bReadSuccess = _FileSystem->FileRead(_FilePath.AsCString(), [&FileContents](size_t ByteSize)
{
return (void*)FileContents.AppendBuffer(ByteSize);
});
if (bReadSuccess)
{
const CUTF8StringView& ContentsView = FileContents.ToStringView();
const int32_t ByteSize = FileContents.ByteLen();
if (ByteSize >= 2 && !(ByteSize & 1) && ((ContentsView[0] == 0xff && ContentsView[1] == 0xfe) || (ContentsView[0] == 0xfe && ContentsView[1] == 0xff)))
{
// It's UTF-16 - we don't support that
return uLang::EResult::Error;
}
if (ByteSize >= 3 && ContentsView[0] == 0xef && ContentsView[1] == 0xbb && ContentsView[2] == 0xbf)
{
// Found UTF-8 BOM, trim it away
return CUTF8String(ContentsView.SubViewTrimBegin(3));
}
return FileContents.MoveToString();
}
return uLang::EResult::Error;
}
//====================================================================================
// CSourceFileModule implementation
//====================================================================================
CUTF8StringView CSourceFileModule::GetDirPath() const
{
CUTF8StringView DirPath, FileName;
FilePathUtils::SplitPath(_FilePath, DirPath, FileName);
return DirPath;
}
TOptional<TSRef<CSourceFileModule>> CSourceFileModule::FindSubmodule(const CUTF8StringView& ModuleName) const
{
TOptional<TSRef<CSourceModule>> Module = _Submodules.FindByKey(ModuleName);
return reinterpret_cast<TOptional<TSRef<CSourceFileModule>>&>(Module);
}
TSRef<CSourceFileModule> CSourceFileModule::FindOrAddSubmodule(const CUTF8StringView& ModuleName, const CUTF8StringView& DirPath)
{
TOptional<TSRef<CSourceFileModule>> Module = FindSubmodule(ModuleName);
if (!Module)
{
Module = TSRef<CSourceFileModule>::New(ModuleName, DirPath);
_Submodules.Add(*Module);
}
return *Module;
}
TOptional<TSRef<CSourceFileSnippet>> CSourceFileModule::FindSnippetByFilePath(const CUTF8StringView& FilePath, bool bRecursive) const
{
TOptional<TSRef<ISourceSnippet>> FoundSnippet;
VisitAll([&FilePath, bRecursive, &FoundSnippet](const CSourceModule& Module)
{
const CUTF8StringView& ModulePath = static_cast<const CSourceFileModule&>(Module).GetDirPath();
if (ModulePath.IsEqualCaseIndependent(FilePath.SubViewBegin(ModulePath.ByteLen())))
{
FoundSnippet = Module._SourceSnippets.FindByPredicate([&FilePath](const ISourceSnippet* Snippet)
{
return static_cast<const CSourceFileSnippet*>(Snippet)->GetFilePath().IsEqualCaseIndependent(FilePath);
});
}
return bRecursive && !FoundSnippet;
});
return reinterpret_cast<TOptional<TSRef<CSourceFileSnippet>>&>(FoundSnippet);
}
//====================================================================================
// CSourceFilePackage implementation
//====================================================================================
bool FromJSON(const JSONValue& JSON, EVerseScope* Value)
{
if (JSON.IsString())
{
const CUTF8StringView ValueString(JSON.GetString(), JSON.GetStringLength());
if (ValueString == "PublicAPI")
{
*Value = EVerseScope::PublicAPI;
return true;
}
if (ValueString == "InternalAPI")
{
*Value = EVerseScope::InternalAPI;
return true;
}
if (ValueString == "PublicUser")
{
*Value = EVerseScope::PublicUser;
return true;
}
if (ValueString == "InternalUser")
{
*Value = EVerseScope::InternalUser;
return true;
}
}
return false;
}
bool FromJSON(const JSONValue& JSON, EPackageRole* Value)
{
if (JSON.IsString())
{
const CUTF8StringView ValueString(JSON.GetString(), JSON.GetStringLength());
TOptional<EPackageRole> MaybeRole = ToPackageRole(ValueString);
if (MaybeRole.IsSet())
{
*Value = *MaybeRole;
return true;
}
}
return false;
}
bool FromJSON(const JSONValue& JSON, CSourcePackage::SSettings* Value)
{
return FromJSON(JSON, "versePath", &Value->_VersePath, false)
&& FromJSON(JSON, "verseScope", &Value->_VerseScope, false)
&& FromJSON(JSON, "dependencyPackages", &Value->_DependencyPackages, false)
&& FromJSON(JSON, "role", &Value->_Role, false)
&& FromJSON(JSON, "verseVersion", &Value->_VerseVersion, false)
&& FromJSON(JSON, "treatModulesAsImplicit", &Value->_bTreatModulesAsImplicit, false)
&& FromJSON(JSON, "vniDestDir", &Value->_VniDestDir, false)
&& FromJSON(JSON, "allowExperimental", &Value->_bAllowExperimental, false);
}
CSourceFilePackage::CSourceFilePackage(const CUTF8String& PackageFilePath, const TSRef<IFileSystem>& FileSystem, const TSRef<CDiagnostics>& Diagnostics)
: CSourcePackage(FilePathUtils::GetNameFromFileOrDir(PackageFilePath),
TSRef<CSourceFileModule>::New("", FilePathUtils::GetDirectory(PackageFilePath, true)))
, _PackageFilePath(PackageFilePath)
, _DirPath(_RootModule->GetFilePath())
, _FileSystem(FileSystem)
{
// Load package file from disk
ReadPackageFile(PackageFilePath, Diagnostics);
// Gather modules and snippets
GatherPackageSourceFiles(PackageFilePath, FileSystem, Diagnostics);
}
CSourceFilePackage::CSourceFilePackage(const SPackageDesc& PackageDesc, const TSRef<IFileSystem>& FileSystem, const TSRef<CDiagnostics>& Diagnostics)
: CSourcePackage(PackageDesc._Name,
TSRef<CSourceFileModule>::New("", FilePathUtils::AppendSlash(PackageDesc._DirPath)))
, _DirPath(PackageDesc._DirPath)
, _FilePaths(PackageDesc._FilePaths)
, _FileSystem(FileSystem)
{
_Settings = PackageDesc._Settings;
// Gather modules and snippets
GatherPackageSourceFiles(PackageDesc._Name, FileSystem, Diagnostics);
}
CSourceFileModule* CSourceFilePackage::GetModuleForFilePath(const CUTF8StringView& FilePath)
{
CSourceFileModule* Result = nullptr;
_RootModule->VisitAll([&FilePath, &Result](CSourceModule& Module)->bool {
CSourceFileModule& FileModule = static_cast<CSourceFileModule&>(Module);
const CUTF8StringView& ModulePath = FileModule.GetDirPath();
if (FilePath.ByteLen() > ModulePath.ByteLen())
{
if (ModulePath == FilePathUtils::GetDirectory(FilePath))
{
Result = &FileModule;
return false;
}
}
return true;
});
return Result;
}
TOptional<TSRef<CSourceFileSnippet>> CSourceFilePackage::FindSnippetByFilePath(const CUTF8StringView& FilePath) const
{
TOptional<TSRef<CSourceFileSnippet>> Result = _RootModule.As<CSourceFileModule>()->FindSnippetByFilePath(FilePath, true);
if (Result)
{
return Result;
}
if (_Digest.IsSet() && _Digest->_Snippet.As<CSourceFileSnippet>()->GetFilePath().IsEqualCaseIndependent(FilePath))
{
return _Digest->_Snippet.As<CSourceFileSnippet>();
}
if (_PublicDigest.IsSet() && _PublicDigest->_Snippet.As<CSourceFileSnippet>()->GetFilePath().IsEqualCaseIndependent(FilePath))
{
return _PublicDigest->_Snippet.As<CSourceFileSnippet>();
}
return EResult::Unspecified;
}
TOptional<TSRef<CSourceFileSnippet>> CSourceFilePackage::AddSnippet(const CUTF8StringView& FilePath)
{
CSourceFileModule* Module = GetModuleForFilePath(FilePath);
if (Module)
{
TSRef<CSourceFileSnippet> Snippet = TSRef<CSourceFileSnippet>::New(FilePath, _FileSystem);
Module->AddSnippet(Snippet);
return Snippet;
}
return EResult::Error;
}
bool CSourceFilePackage::RemoveSnippet(const CUTF8StringView& FilePath)
{
CSourceFileModule* Module = GetModuleForFilePath(FilePath);
if (Module)
{
TOptional<TSRef<CSourceFileSnippet>> Snippet = Module->FindSnippetByFilePath(FilePath, false);
if (Snippet)
{
Module->RemoveSnippet(*Snippet, false);
return true;
}
}
return false;
}
void CSourceFilePackage::ReadPackageFile(const CUTF8String& PackageFilePath, const TSRef<CDiagnostics>& Diagnostics)
{
// Parse package file and settings
if (!CSourceFileProject::IsPackageFile(PackageFilePath))
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_BadPackageFileName,
CUTF8String("Package file `%s` has incorrect file extension.", *PackageFilePath) });
}
CUTF8StringBuilder PackageFileContents;
const bool bReadSuccess = _FileSystem->FileRead(PackageFilePath.AsCString(), [&PackageFileContents](size_t ByteSize)
{
return (void*)PackageFileContents.AppendBuffer(ByteSize);
});
if (bReadSuccess)
{
// Set up RapidJSON document
JSONAllocator Allocator;
JSONMemoryPoolAllocator MemoryPoolAllocator(RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY, &Allocator);
const size_t StackCapacity = 1024;
JSONDocument PackageDocument(&MemoryPoolAllocator, StackCapacity, &Allocator);
// Parse package file into document
PackageDocument.Parse(PackageFileContents.AsCString(), PackageFileContents.ByteLen());
if (!PackageDocument.HasParseError() && FromJSON(PackageDocument, &_Settings))
{
if (_Settings._VniDestDir)
{
// Fully qualify VNI directory
_Settings._VniDestDir.Emplace(FilePathUtils::ConvertRelativePathToFull(*_Settings._VniDestDir, _DirPath));
}
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSyntax_MalformedPackageFile,
CUTF8String("Cannot parse contents of package file `%s`.", *PackageFilePath) });
}
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::WarnSystem_CannotReadPackage,
CUTF8String("Unable to read package file `%s`.", *PackageFilePath) });
}
}
TSRef<CSourceFileModule> CSourceFilePackage::ResolveModuleForRelativeVersePath(const CUTF8String& RelativeVersePath, const TSRef<CDiagnostics>& Diagnostics) const
{
TSRef<CSourceFileModule> Module = _RootModule.As<CSourceFileModule>();
FilePathUtils::ForeachPartOfPath(RelativeVersePath, [&Module, &RelativeVersePath, &Diagnostics](const CUTF8StringView& Part)
{
if (Part.IsFilled()) // If the path component is relative, just check the next component of the path.
{
if (Part == ".." || Part == ".")
{
return;
}
if (CSourceFileProject::IsValidModuleName(Part))
{
Module = Module->FindOrAddSubmodule(Part, FilePathUtils::AppendSlash(FilePathUtils::CombinePaths(Module->GetDirPath(), Part)));
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_InvalidModuleName,
CUTF8String("The relative Verse path `%s` contains disallowed characters that would lead to the invalid module name `%s`.", *RelativeVersePath, *CUTF8String(Part)) });
}
}
});
return Module;
}
void CSourceFilePackage::GatherPackageSourceFiles(const CUTF8String& PackageFilePath, const TSRef<IFileSystem>& FileSystem, const TSRef<CDiagnostics>& Diagnostics)
{
TArray<CUTF8String> SourceFilePaths;
TArray<CUTF8String> StrayPackageFilePaths; // Additional package files found underneath package
// Helper to process a single file discovered on disk
auto ProcessFile = [this, &PackageFilePath, &StrayPackageFilePaths, &Diagnostics, &FileSystem](const CUTF8String& FilePath)
{
CUTF8String NormalizedFilePath = FilePathUtils::NormalizePath(FilePath);
bool bIsSnippetFile = CSourceFileProject::IsSnippetFile(NormalizedFilePath);
bool bIsModuleFile = CSourceFileProject::IsModuleFile(NormalizedFilePath);
if (bIsSnippetFile || bIsModuleFile)
{
// Find or create module for this file
CUTF8String RelativeFilePath = FilePathUtils::ConvertFullPathToRelative(NormalizedFilePath, _DirPath);
if (!ULANG_ENSUREF(RelativeFilePath != NormalizedFilePath, "File path `%s` appears to be not under package directory `%s`", *NormalizedFilePath, *_DirPath))
{
return;
}
if (bIsSnippetFile)
{
TSRef<CSourceFileSnippet> Snippet = TSRef<CSourceFileSnippet>::New(Move(NormalizedFilePath), FileSystem);
if (Snippet->GetFilePath().EndsWith(".digest.verse"))
{
if (_Digest.IsSet())
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_DuplicateDigestFile,
CUTF8String("Found duplicate digest `%s` for package `%s` when digest `%s` already exists.", *Snippet->GetPath(), *GetName(), *_Digest->_Snippet->GetPath())});
}
else
{
_Digest.Emplace(SVersionedDigest{Move(Snippet), _Settings._VerseVersion.Get(Verse::Version::Default)});
}
}
else
{
bool bIsVNIPackage = _Settings._VniDestDir.IsSet();
bool bHasNativeFileExtension = Snippet->GetFilePath().EndsWith(".native.verse");
CUTF8StringView Dir;
CUTF8StringView FileName;
FilePathUtils::SplitPath(Snippet->GetFilePath(), Dir, FileName);
CUTF8StringView Stem;
CUTF8StringView Extension;
FilePathUtils::SplitFileName(FileName, Stem, Extension);
if (!VerseFN::UploadedAtFNVersion::EnforceSnippetNameValidity(_Settings._UploadedAtFNVersion.Get(VerseFN::UploadedAtFNVersion::Latest))
&& !bHasNativeFileExtension)
{
if (Stem.Contains('.'))
{
return;
}
}
if (VerseFN::UploadedAtFNVersion::EnforceSnippetNameValidity(_Settings._UploadedAtFNVersion.Get(VerseFN::UploadedAtFNVersion::Latest))
&& !CSourceFileProject::IsValidSnippetFileName(FileName))
{
Diagnostics->AppendGlitch({EDiagnostic::ErrSystem_BadSnippetFileName,
CUTF8String("Verse file `%s` does not have a valid snippet name. Verse snippet names must end in `.verse` and cannot contain any of the following characters: %s.", *Snippet->GetPath(), CSourceFileSnippet::_InvalidSnippetCharacters)});
}
if (bIsVNIPackage && !bHasNativeFileExtension)
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_InconsistentNativeFileExtension,
CUTF8String("Verse file `%s` is in VNI-capable package `%s`, therefore should have the `.native.verse` file extension.", *Snippet->GetPath(), *GetName()) });
}
else if (!bIsVNIPackage && bHasNativeFileExtension)
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_InconsistentNativeFileExtension,
CUTF8String("Verse file `%s` is in non-VNI-capable package `%s`, therefore should not have the `.native.verse` file extension.", *Snippet->GetPath(), *GetName()) });
}
TSRef<CSourceFileModule> Module = ResolveModuleForRelativeVersePath(FilePathUtils::GetDirectory(RelativeFilePath), Diagnostics);
Module->AddSnippet(Snippet);
}
}
else
{
ULANG_ASSERTF(bIsModuleFile, "Must be a module when we get here.");
#if VERSE_ALLOW_VMODULE_FILES
// Find module based on its path
TSRef<CSourceFileModule> Module = ResolveModuleForRelativeVersePath(FilePathUtils::GetDirectory(RelativeFilePath), Diagnostics);
// Then gather settings and store the vmodule file path
Module->_FilePath = Move(NormalizedFilePath);
#else
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_InvalidModuleFile,
CUTF8String("Found vmodule file `%s` which is not allowed.", *NormalizedFilePath) });
#endif
}
}
else if (CSourceFileProject::IsPackageFile(FilePath) && FilePathUtils::NormalizePath(PackageFilePath) != NormalizedFilePath)
{
// Keep track of stray package files for sanity checking
StrayPackageFilePaths.Add(NormalizedFilePath);
}
};
// 1) Gather named modules and files
if (_FilePaths.IsSet())
{
for (const CUTF8String& FilePath : *_FilePaths)
{
ProcessFile(FilePath);
}
}
else
{
FileSystem->IterateDirectory(_DirPath.AsCString(), /*bRecursive =*/true,
[&ProcessFile](const CUTF8StringView& FileName, const CUTF8StringView& Path, bool bIsDirectory)
{
if (!bIsDirectory)
{
ProcessFile(FilePathUtils::CombinePaths(Path, FileName));
}
return true; // continue iteration
});
}
// 2) Report stray package files encountered
for (const CUTF8String& StrayPackageFilePath : StrayPackageFilePaths)
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_IllegalSubPackage,
CUTF8String("Found illegal additional vpackage `%s` underneath package `%s`.", *StrayPackageFilePath , *PackageFilePath) });
}
// 3) Handle legacy named module files and adjust the module hierarchy accordingly
#if VERSE_ALLOW_VMODULE_FILES
auto TryRenameModule = [&Diagnostics](const TSRef<CSourceFileModule>& Module, CSourceFileModule* RenamedParent, bool bHasRenamedParent, auto& TryRenameModule) -> bool
{
bool bRenamed = false;
// Does this module have a legacy name override?
CUTF8StringView NameOverride = Module->GetNameFromFile();
if (NameOverride.IsFilled())
{
// Yes rename, and make new parent for renamed submodules
bRenamed = true;
if (CSourceFileProject::IsValidModuleName(NameOverride))
{
Module->_Name = NameOverride;
RenamedParent = Module;
bHasRenamedParent = true;
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_InvalidModuleName,
CUTF8String("The path of the file `%s` contains disallowed characters that would lead to the invalid module name `%s`.", *Module->GetFilePath() , *CUTF8String(NameOverride)) });
}
}
// Recurse into submodules
for (int32_t Index = Module->_Submodules.Num() - 1; Index >= 0; --Index)
{
TSRef<CSourceFileModule> Submodule = Module->_Submodules[Index].As<CSourceFileModule>();
if (TryRenameModule(Submodule, RenamedParent, bHasRenamedParent, TryRenameModule))
{
// Submodule was renamed, reparent it to the nearest renamed parent
Module->_Submodules.RemoveAt(Index);
RenamedParent->_Submodules.Add(Submodule);
}
else if (bHasRenamedParent)
{
// Delete submodule and move its snippets to the nearest renamed parent
Module->_Submodules.RemoveAt(Index);
RenamedParent->_SourceSnippets.Append(Move(Submodule->_SourceSnippets));
ULANG_ENSUREF(Submodule->_Submodules.IsEmpty(), "Submodule must not have any submodules of its own left at this point.");
}
}
return bRenamed;
};
if (TryRenameModule(_RootModule.As<CSourceFileModule>(), _RootModule.As<CSourceFileModule>(), false, TryRenameModule))
{
TSRef<CSourceFileModule> NewRootModule = TSRef<CSourceFileModule>::New("", FilePathUtils::AppendSlash(_RootModule.As<CSourceFileModule>()->GetDirPath()));
NewRootModule->_Submodules.Add(_RootModule);
_RootModule = NewRootModule;
}
#endif
}
//====================================================================================
// CSourceFileProject implementation
//====================================================================================
bool FromJSON(const JSONValue& JSON, SPackageDesc* Value)
{
return FromJSON(JSON, "name", &Value->_Name, true)
&& FromJSON(JSON, "dirPath", &Value->_DirPath, true)
&& FromJSON(JSON, "filePaths", &Value->_FilePaths, false)
&& FromJSON(JSON, "settings", &Value->_Settings, true);
}
bool FromJSON(const JSONValue& JSON, SPackageRef* Value)
{
// Set optional values prior to read
Value->_ReadOnly = false;
Value->_Build = true;
// There has to be one of path or desc
bool HavePath = FromJSON(JSON, "path", &Value->_FilePath, false);
return FromJSON(JSON, "desc", &Value->_Desc, !HavePath)
&& FromJSON(JSON, "readOnly", &Value->_ReadOnly, false)
&& FromJSON(JSON, "build", &Value->_Build, false);
}
bool FromJSON(const JSONValue& JSON, SProjectDesc* Value)
{
return FromJSON(JSON, "packages", &Value->_Packages, true);
}
CSourceFileProject::CSourceFileProject(const CUTF8String& ProjectFilePath, const TSRef<IFileSystem>& FileSystem, const TSRef<CDiagnostics>& Diagnostics)
: CSourceProject(FilePathUtils::GetNameFromFileOrDir(ProjectFilePath))
, _FilePath(ProjectFilePath)
, _FileSystem(FileSystem)
{
// Parse project file and load packages specified in it
CUTF8StringBuilder ProjectFileContents;
const bool bReadSuccess = _FileSystem->FileRead(ProjectFilePath.AsCString(), [&ProjectFileContents](size_t ByteSize)
{
return (void*)ProjectFileContents.AppendBuffer(ByteSize);
});
if (bReadSuccess)
{
// Set up RapidJSON document
JSONAllocator Allocator;
JSONMemoryPoolAllocator MemoryPoolAllocator(RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY, &Allocator);
const size_t StackCapacity = 1024;
JSONDocument ProjectDocument(&MemoryPoolAllocator, StackCapacity, &Allocator);
// Parse project file into document
SProjectDesc ProjectDesc;
ProjectDocument.Parse(ProjectFileContents.AsCString(), ProjectFileContents.ByteLen());
if (!ProjectDocument.HasParseError() && FromJSON(ProjectDocument, &ProjectDesc))
{
for (const SPackageRef& PackageRef : ProjectDesc._Packages)
{
// Add packages to build and skip packages not to build. [Could alternatively pass along `_Build` setting.]
if (PackageRef._Build)
{
if (PackageRef._FilePath)
{
CUTF8String PackageFilePath = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(*PackageRef._FilePath, FilePathUtils::GetDirectory(ProjectFilePath)));
TSRef<CSourceFilePackage> NewPackage = TSRef<CSourceFilePackage>::New(PackageFilePath, _FileSystem, Diagnostics);
_Packages.Add({ NewPackage, PackageRef._ReadOnly });
}
else if (ULANG_ENSUREF(PackageRef._Desc, "FromJSON must ensure that there is either a file path or a descriptor."))
{
SPackageDesc FullDesc = *PackageRef._Desc;
FullDesc._DirPath = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(FullDesc._DirPath, FilePathUtils::GetDirectory(ProjectFilePath)));
if (!_FileSystem.Get()->DoesDirectoryExist(FullDesc._DirPath.AsCString()))
{
continue;
}
if (FullDesc._Settings._VniDestDir)
{
FullDesc._Settings._VniDestDir = FilePathUtils::NormalizePath(FilePathUtils::ConvertRelativePathToFull(*FullDesc._Settings._VniDestDir, FilePathUtils::GetDirectory(ProjectFilePath)));
}
TSRef<CSourceFilePackage> NewPackage = TSRef<CSourceFilePackage>::New(FullDesc, _FileSystem, Diagnostics);
_Packages.Add({ NewPackage, PackageRef._ReadOnly });
}
}
}
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSyntax_MalformedProjectFile,
CUTF8String("Cannot parse contents of project file `%s`.", *ProjectFilePath) });
}
}
else
{
Diagnostics->AppendGlitch({
EDiagnostic::ErrSystem_CannotReadText,
CUTF8String("Unable to read project file `%s`.", *ProjectFilePath) });
}
}
CSourceFileProject::CSourceFileProject(
const CUTF8String& Name,
const TSRef<IFileSystem>& FileSystem,
const TArray<SPackageDesc>& Packages,
const TSRef<CDiagnostics>& Diagnostics)
: CSourceProject(Name)
, _FileSystem(FileSystem)
{
if (Packages.IsEmpty())
{
Diagnostics->AppendGlitch({ EDiagnostic::WarnProject_EmptyProject });
}
else
{
// Assemble project packages
for (const SPackageDesc& Package : Packages)
{
TSRef<CSourceFilePackage> NewPackage = TSRef<CSourceFilePackage>::New(Package, _FileSystem, Diagnostics);
const bool bIsReadOnly = false;
_Packages.Add({ NewPackage, bIsReadOnly });
}
}
}
bool CSourceFileProject::WriteProjectFile(const CUTF8String& ProjectFilePath, const TSRef<CDiagnostics>& Diagnostics)
{
CSourceProjectWriter Writer(_FileSystem, Diagnostics);
return Writer.WriteProjectFile(CSourceProjectWriter::GetProjectDesc(*this), ProjectFilePath);
}
bool CSourceFileProject::WriteVSCodeWorkspaceFile(const CUTF8String& WorkspaceFilePath, const CUTF8String& ProjectFilePath, const TSRef<CDiagnostics>& Diagnostics)
{
CSourceProjectWriter Writer(_FileSystem, Diagnostics);
return Writer.WriteVSCodeWorkspaceFile(CSourceProjectWriter::GetWorkspaceDesc(*this, ProjectFilePath), WorkspaceFilePath);
}
TOptional<TSRef<CSourceFileSnippet>> CSourceFileProject::FindSnippetByFilePath(const CUTF8StringView& FilePath) const
{
for (const SPackage& Package : _Packages)
{
TOptional<TSRef<CSourceFileSnippet>> Snippet = Package._Package.As<CSourceFilePackage>()->FindSnippetByFilePath(FilePath);
if (Snippet)
{
return Snippet;
}
}
return EResult::Unspecified;
}
TOptional<TSRef<CSourceFileSnippet>> CSourceFileProject::AddSnippet(const CUTF8StringView& FilePath)
{
for (const SPackage& Package : _Packages)
{
TOptional<TSRef<CSourceFileSnippet>> Snippet = Package._Package.As<CSourceFilePackage>()->AddSnippet(FilePath);
if (Snippet)
{
return Snippet;
}
}
return EResult::Error;
}
bool CSourceFileProject::RemoveSnippet(const CUTF8StringView& FilePath)
{
for (const SPackage& Package : _Packages)
{
if (Package._Package.As<CSourceFilePackage>()->RemoveSnippet(FilePath))
{
return true;
}
}
return false;
}
bool CSourceFileProject::IsSnippetFile(const CUTF8StringView& FilePath)
{
return FilePath.SubViewEnd(SnippetExt.ByteLen()) == SnippetExt;
}
bool CSourceFileProject::IsModuleFile(const CUTF8StringView& FilePath)
{
return FilePath.SubViewEnd(ModuleExt.ByteLen()) == ModuleExt;
}
bool CSourceFileProject::IsPackageFile(const CUTF8StringView& FilePath)
{
return FilePath.SubViewEnd(PackageExt.ByteLen()) == PackageExt;
}
bool CSourceFileProject::IsProjectFile(const CUTF8StringView& FilePath)
{
return FilePath.SubViewEnd(ProjectExt.ByteLen()) == ProjectExt;
}
bool CSourceFileProject::IsValidModuleName(const CUTF8StringView& ModuleName)
{
const UTF8Char FirstCh = ModuleName.FirstByte();
if (!CUnicode::IsAlphaASCII(FirstCh) && FirstCh != '_')
{
return false;
}
for (const UTF8Char* Ch = ModuleName._Begin; Ch < ModuleName._End; ++Ch)
{
if (!CUnicode::IsAlphaASCII(*Ch) && !CUnicode::IsDigitASCII(*Ch) && *Ch != '_')
{
return false;
}
}
return true;
}
bool CSourceFileProject::IsValidSnippetFileName(const CUTF8StringView& FileName)
{
const char* InvalidCharacters = CSourceFileSnippet::_InvalidSnippetCharacters;
for (char Ch = *InvalidCharacters; Ch != '\0'; Ch = *(++InvalidCharacters))
{
if (FileName.Contains(Ch))
{
return false;
}
}
if (FileName.EndsWith(SnippetExt))
{
return true;
}
return false;
}
}
// namespace uLang