bool FVisualStudioSourceCodeAccessor::AddSourceFiles(const TArray<FString>& AbsoluteSourcePaths, const TArray<FString>& AvailableModules) { // This requires DTE - there is no fallback for this operation when DTE is not available #if VSACCESSOR_HAS_DTE bool bSuccess = true; struct FModuleNameAndPath { FString ModuleBuildFilePath; FString ModulePath; FName ModuleName; }; TArray<FModuleNameAndPath> ModuleNamesAndPaths; ModuleNamesAndPaths.Reserve(AvailableModules.Num()); for (const FString& AvailableModule : AvailableModules) { static const int32 BuildFileExtensionLen = FString(TEXT(".Build.cs")).Len(); // AvailableModule is the relative path to the .Build.cs file FModuleNameAndPath ModuleNameAndPath; ModuleNameAndPath.ModuleBuildFilePath = FPaths::ConvertRelativePathToFull(AvailableModule); ModuleNameAndPath.ModulePath = FPaths::GetPath(ModuleNameAndPath.ModuleBuildFilePath); ModuleNameAndPath.ModuleName = *FPaths::GetCleanFilename(ModuleNameAndPath.ModuleBuildFilePath).LeftChop(BuildFileExtensionLen); ModuleNamesAndPaths.Add(ModuleNameAndPath); } struct FModuleNewSourceFiles { FModuleNameAndPath ModuleNameAndPath; TArray<FString> NewSourceFiles; }; // Work out which module each source file will be in TMap<FName, FModuleNewSourceFiles> ModuleToNewSourceFiles; { const FModuleNameAndPath* LastSourceFilesModule = nullptr; for (const FString& SourceFile : AbsoluteSourcePaths) { // First check to see if this source file is in the same module as the last source file - this is usually the case, and saves us a lot of string compares if (LastSourceFilesModule && SourceFile.StartsWith(LastSourceFilesModule->ModulePath)) { FModuleNewSourceFiles& ModuleNewSourceFiles = ModuleToNewSourceFiles.FindChecked(LastSourceFilesModule->ModuleName); ModuleNewSourceFiles.NewSourceFiles.Add(SourceFile); continue; } // Look for the module which will contain this file LastSourceFilesModule = nullptr; for (const FModuleNameAndPath& ModuleNameAndPath : ModuleNamesAndPaths) { if (SourceFile.StartsWith(ModuleNameAndPath.ModulePath)) { LastSourceFilesModule = &ModuleNameAndPath; FModuleNewSourceFiles& ModuleNewSourceFiles = ModuleToNewSourceFiles.FindOrAdd(ModuleNameAndPath.ModuleName); ModuleNewSourceFiles.ModuleNameAndPath = ModuleNameAndPath; ModuleNewSourceFiles.NewSourceFiles.Add(SourceFile); break; } } // Failed to find the module for this source file? if (!LastSourceFilesModule) { UE_LOG(LogVSAccessor, Warning, TEXT("Cannot add source file '%s' as it doesn't belong to a known module"), *SourceFile); bSuccess = false; } } } TComPtr<EnvDTE::_DTE> DTE; const FString SolutionPath = GetSolutionPath(); if (AccessVisualStudioViaDTE(DTE, SolutionPath, GetPrioritizedVisualStudioVersions(SolutionPath)) == EAccessVisualStudioResult::VSInstanceIsOpen) { TComPtr<EnvDTE::_Solution> Solution; if (SUCCEEDED(DTE->get_Solution(&Solution)) && Solution) { // Process each module for (const auto& ModuleNewSourceFilesKeyValue : ModuleToNewSourceFiles) { const FModuleNewSourceFiles& ModuleNewSourceFiles = ModuleNewSourceFilesKeyValue.Value; const FString& ModuleBuildFilePath = ModuleNewSourceFiles.ModuleNameAndPath.ModuleBuildFilePath; auto ANSIModuleBuildFilePath = StringCast<ANSICHAR>(*ModuleBuildFilePath); FComBSTR COMStrModuleBuildFilePath(ANSIModuleBuildFilePath.Get()); TComPtr<EnvDTE::ProjectItem> BuildFileProjectItem; if (SUCCEEDED(Solution->FindProjectItem(COMStrModuleBuildFilePath, &BuildFileProjectItem)) && BuildFileProjectItem) { // We found the .Build.cs file in the existing solution - now we need its parent ProjectItems as that's what we'll be adding new content to TComPtr<EnvDTE::ProjectItems> ModuleProjectFolder; if (SUCCEEDED(BuildFileProjectItem->get_Collection(&ModuleProjectFolder)) && ModuleProjectFolder) { for (const FString& SourceFile : AbsoluteSourcePaths) { const FString ProjectRelativeSourceFilePath = SourceFile.Mid(ModuleNewSourceFiles.ModuleNameAndPath.ModulePath.Len()); TArray<FString> SourceFileParts; ProjectRelativeSourceFilePath.ParseIntoArray(SourceFileParts, TEXT("/"), true); if (SourceFileParts.Num() == 0) { // This should never happen as it means we somehow have no filename within the project directory bSuccess = false; continue; } TComPtr<EnvDTE::ProjectItems> CurProjectItems = ModuleProjectFolder; // Firstly we need to make sure that all the folders we need exist - this also walks us down to the correct place to add the file for (int32 FilePartIndex = 0; FilePartIndex < SourceFileParts.Num() - 1 && CurProjectItems; ++FilePartIndex) { const FString& SourceFilePart = SourceFileParts[FilePartIndex]; auto ANSIPart = StringCast<ANSICHAR>(*SourceFilePart); FComBSTR COMStrFilePart(ANSIPart.Get()); ::VARIANT vProjectItemName; vProjectItemName.vt = VT_BSTR; vProjectItemName.bstrVal = COMStrFilePart; TComPtr<EnvDTE::ProjectItem> ProjectItem; if (SUCCEEDED(CurProjectItems->Item(vProjectItemName, &ProjectItem)) && !ProjectItem) { // Add this part CurProjectItems->AddFolder(COMStrFilePart, nullptr, &ProjectItem); } if (ProjectItem) { ProjectItem->get_ProjectItems(&CurProjectItems); } else { CurProjectItems = nullptr; } } if (!CurProjectItems) { // Failed to find or add all the path parts bSuccess = false; continue; } // Now we add the file to the project under the last folder we found along its path auto ANSIPath = StringCast<ANSICHAR>(*SourceFile); FComBSTR COMStrFileName(ANSIPath.Get()); TComPtr<EnvDTE::ProjectItem> FileProjectItem; if (SUCCEEDED(CurProjectItems->AddFromFile(COMStrFileName, &FileProjectItem))) { bSuccess &= true; } } // Save the updated project to avoid a message when closing VS TComPtr<EnvDTE::Project> Project; if (SUCCEEDED(ModuleProjectFolder->get_ContainingProject(&Project)) && Project) { Project->Save(nullptr); } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Cannot add source files as we failed to get the parent items container for the '%s' item"), *ModuleBuildFilePath); bSuccess = false; } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Cannot add source files as we failed to find '%s' in the solution"), *ModuleBuildFilePath); bSuccess = false; } } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Cannot add source files as Visual Studio failed to return a solution when queried")); bSuccess = false; } } else { UE_LOG(LogVSAccessor, Verbose, TEXT("Cannot add source files as Visual Studio is either not open or not responding")); bSuccess = false; } return bSuccess; #endif return false; }
/** Accesses the correct visual studio instance if possible. */ EAccessVisualStudioResult AccessVisualStudioViaDTE(TComPtr<EnvDTE::_DTE>& OutDTE, const FString& InSolutionPath, const TArray<FVisualStudioSourceCodeAccessor::VisualStudioLocation>& InLocations) { EAccessVisualStudioResult AccessResult = EAccessVisualStudioResult::VSInstanceIsNotOpen; // Open the Running Object Table (ROT) IRunningObjectTable* RunningObjectTable; if(SUCCEEDED(GetRunningObjectTable(0, &RunningObjectTable)) && RunningObjectTable) { IEnumMoniker* MonikersTable; if(SUCCEEDED(RunningObjectTable->EnumRunning(&MonikersTable))) { MonikersTable->Reset(); // Look for all visual studio instances in the ROT IMoniker* CurrentMoniker; while(AccessResult != EAccessVisualStudioResult::VSInstanceIsOpen && MonikersTable->Next(1, &CurrentMoniker, NULL) == S_OK) { IBindCtx* BindContext; LPOLESTR OutName; if(SUCCEEDED(CreateBindCtx(0, &BindContext)) && SUCCEEDED(CurrentMoniker->GetDisplayName(BindContext, NULL, &OutName))) { if(IsVisualStudioDTEMoniker(FString(OutName), InLocations)) { TComPtr<IUnknown> ComObject; if(SUCCEEDED(RunningObjectTable->GetObject(CurrentMoniker, &ComObject))) { TComPtr<EnvDTE::_DTE> TempDTE; if (SUCCEEDED(TempDTE.FromQueryInterface(__uuidof(EnvDTE::_DTE), ComObject))) { // Get the solution path for this instance // If it equals the solution we would have opened above in RunVisualStudio(), we'll take that TComPtr<EnvDTE::_Solution> Solution; BSTR OutPath = nullptr; if (SUCCEEDED(TempDTE->get_Solution(&Solution)) && SUCCEEDED(Solution->get_FullName(&OutPath))) { FString Filename(OutPath); FPaths::NormalizeFilename(Filename); if (Filename == InSolutionPath) { OutDTE = TempDTE; AccessResult = EAccessVisualStudioResult::VSInstanceIsOpen; } SysFreeString(OutPath); } else { UE_LOG(LogVSAccessor, Warning, TEXT("Visual Studio is open but could not be queried - it may be blocked by a modal operation")); AccessResult = EAccessVisualStudioResult::VSInstanceIsBlocked; } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Could not get DTE interface from returned Visual Studio instance")); AccessResult = EAccessVisualStudioResult::VSInstanceIsBlocked; } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Couldn't get Visual Studio COM object")); AccessResult = EAccessVisualStudioResult::VSInstanceUnknown; } } } else { UE_LOG(LogVSAccessor, Warning, TEXT("Couldn't get display name")); AccessResult = EAccessVisualStudioResult::VSInstanceUnknown; } BindContext->Release(); CurrentMoniker->Release(); } MonikersTable->Release(); } else { UE_LOG(LogVSAccessor, Warning, TEXT("Couldn't enumerate ROT table")); AccessResult = EAccessVisualStudioResult::VSInstanceUnknown; } RunningObjectTable->Release(); } else { UE_LOG(LogVSAccessor, Warning, TEXT("Couldn't get ROT table")); AccessResult = EAccessVisualStudioResult::VSInstanceUnknown; } return AccessResult; }