コード例 #1
0
TSharedRef<SDockTab> FMerge::GenerateMergeWidget(const UBlueprint& Object, TSharedRef<FBlueprintEditor> Editor)
{
	auto ActiveTabPtr = ActiveTab.Pin();
	if( ActiveTabPtr.IsValid() )
	{
		// just bring the tab to the foreground:
		auto CurrentTab = FGlobalTabmanager::Get()->InvokeTab(MergeToolTabId);
		check( CurrentTab == ActiveTabPtr );
		return ActiveTabPtr.ToSharedRef();
	}

	// merge the local asset with the depot, SCC provides us with the last common revision as
	// a basis for the merge:

	TSharedPtr<SWidget> Contents;

	if (!PendingMerge(Object))
	{
		// this should load up the merge-tool, with an asset picker, where they
		// can pick the asset/revisions to merge against
		Contents = GenerateMergeTabContents(Editor, nullptr, FRevisionInfo::InvalidRevision(), nullptr, FRevisionInfo::InvalidRevision(), &Object, FOnMergeResolved());
	}
	else
	{
		// @todo DO: this will probably need to be async.. pulling down some old versions of assets:
		const FString& PackageName = Object.GetOutermost()->GetName();
		const FString& AssetName = Object.GetName();

		FSourceControlStatePtr SourceControlState = FMergeToolUtils::GetSourceControlState(PackageName);
		if (!SourceControlState.IsValid())
		{
			DisplayErrorMessage(
				FText::Format(
					LOCTEXT("MergeFailedNoSourceControl", "Aborted Load of {0} from {1} because the source control state was invalidated")
					, FText::FromString(AssetName)
					, FText::FromString(PackageName)
				)
			);

			Contents = SNew(SHorizontalBox);
		}
		else
		{
			ISourceControlState const& SourceControlStateRef = *SourceControlState;

			FRevisionInfo CurrentRevInfo = FRevisionInfo::InvalidRevision();
			const UBlueprint* RemoteBlueprint = Cast< UBlueprint >(LoadHeadRev(PackageName, AssetName, SourceControlStateRef, CurrentRevInfo));
			FRevisionInfo BaseRevInfo = FRevisionInfo::InvalidRevision();
			const UBlueprint* BaseBlueprint = Cast< UBlueprint >(LoadBaseRev(PackageName, AssetName, SourceControlStateRef, BaseRevInfo));

			Contents = GenerateMergeTabContents(Editor, BaseBlueprint, BaseRevInfo, RemoteBlueprint, CurrentRevInfo, &Object, FOnMergeResolved());
		}
	}

	TSharedRef<SDockTab> Tab =  FGlobalTabmanager::Get()->InvokeTab(MergeToolTabId);
	Tab->SetContent(Contents.ToSharedRef());
	ActiveTab = Tab;
	return Tab;

}
コード例 #2
0
	/**
	 * Simulates the user pressing the blueprint's compile button (will load the
	 * blueprint first if it isn't already).
	 * 
	 * @param  BlueprintAssetPath	The asset object path that you wish to compile.
	 * @return False if we failed to load the blueprint, true otherwise
	 */
	static bool CompileBlueprint(const FString& BlueprintAssetPath)
	{
		UBlueprint* BlueprintObj = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));
		if (!BlueprintObj || !BlueprintObj->ParentClass)
		{
			UE_LOG(LogBlueprintAutomationTests, Error, TEXT("Failed to compile invalid blueprint, or blueprint parent no longer exists."));
			return false;
		}

		UPackage* const BlueprintPackage = Cast<UPackage>(BlueprintObj->GetOutermost());
		// compiling the blueprint will inherently dirty the package, but if there 
		// weren't any changes to save before, there shouldn't be after
		bool const bStartedWithUnsavedChanges = (BlueprintPackage != nullptr) ? BlueprintPackage->IsDirty() : true;

		bool bIsRegeneratingOnLoad = false;
		bool bSkipGarbageCollection = true;
		FBlueprintEditorUtils::RefreshAllNodes(BlueprintObj);
		FKismetEditorUtilities::CompileBlueprint(BlueprintObj, bIsRegeneratingOnLoad, bSkipGarbageCollection);

		if (BlueprintPackage != nullptr)
		{
			BlueprintPackage->SetDirtyFlag(bStartedWithUnsavedChanges);
		}

		return true;
	}
コード例 #3
0
bool FMerge::PendingMerge(const UBlueprint& BlueprintObj) const
{
	ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();

	bool bPendingMerge = false;
	if( SourceControlProvider.IsEnabled() )
	{
		FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(BlueprintObj.GetOutermost(), EStateCacheUsage::Use);
		bPendingMerge = SourceControlState.IsValid() && SourceControlState->IsConflicted();
	}
	return bPendingMerge;
}
コード例 #4
0
/** 
 * Runs compile-on-load test against all unloaded, and optionally loaded, blueprints
 * See the TestAllBlueprints config key in the [Automation.Blueprint] config sections
 */
bool FBlueprintCompileOnLoadTest::RunTest(const FString& BlueprintAssetPath)
{
	FCompilerResultsLog Results;

	UBlueprint* ExistingBP = nullptr;
	// if this blueprint was already loaded, then these tests are invalidated 
	// (because dependencies have already been loaded)
	if (FBlueprintAutomationTestUtilities::IsBlueprintLoaded(BlueprintAssetPath, &ExistingBP))
	{
		if (FBlueprintAutomationTestUtilities::IsAssetUnsaved(BlueprintAssetPath))
		{
			AddError(FString::Printf(TEXT("You have unsaved changes made to '%s', please save them before running this test."), *BlueprintAssetPath));
			return false;
		}
		else
		{
			AddWarning(FString::Printf(TEXT("Test may be invalid (the blueprint is already loaded): '%s'"), *BlueprintAssetPath));
			FBlueprintAutomationTestUtilities::UnloadBlueprint(ExistingBP);
		}
	}

	// tracks blueprints that were already loaded (and cleans up any that were 
	// loaded in its lifetime, once it is destroyed)
	FScopedBlueprintUnloader NewBlueprintUnloader(/*bAutoOpenScope =*/true, /*bRunGCOnCloseIn =*/true);

	// We load the blueprint twice and compare the two for discrepancies. This is 
	// to bring dependency load issues to light (among other things). If a blueprint's
	// dependencies are loaded too late, then this first object is the degenerate one.
	UBlueprint* InitialBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));

	// if we failed to load it the first time, then there is no need to make a 
	// second attempt, leave them to fix up this issue first
	if (InitialBlueprint == NULL)
	{
		AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'"), *BlueprintAssetPath));
		return false;
	}

	if (!InitialBlueprint->SkeletonGeneratedClass || !InitialBlueprint->GeneratedClass)
	{
		AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'. Probably it derives from an invalid class."), *BlueprintAssetPath));
		return false;
	}

	// GATHER SUBOBJECTS
	TArray<TWeakObjectPtr<UObject>> InitialBlueprintSubobjects;
	{
		TArray<UObject*> InitialBlueprintSubobjectsPtr;
		GetObjectsWithOuter(InitialBlueprint, InitialBlueprintSubobjectsPtr);
		for (auto Obj : InitialBlueprintSubobjectsPtr)
		{
			InitialBlueprintSubobjects.Add(Obj);
		}
	}

	// GATHER DEPENDENCIES
	TSet<TWeakObjectPtr<UBlueprint>> BlueprintDependencies;
	{
		TArray<UBlueprint*> DependentBlueprints;
		FBlueprintEditorUtils::GetDependentBlueprints(InitialBlueprint, DependentBlueprints);
		for (auto BP : DependentBlueprints)
		{
			BlueprintDependencies.Add(BP);
		}
	}
	BlueprintDependencies.Add(InitialBlueprint);

	// GATHER DEPENDENCIES PERSISTENT DATA
	struct FReplaceInnerData
	{
		TWeakObjectPtr<UClass> Class;
		FStringAssetReference BlueprintAsset;
	};
	TArray<FReplaceInnerData> ReplaceInnerData;
	for (auto BPToUnloadWP : BlueprintDependencies)
	{
		auto BPToUnload = BPToUnloadWP.Get();
		auto OldClass = BPToUnload ? *BPToUnload->GeneratedClass : NULL;
		if (OldClass)
		{
			FReplaceInnerData Data;
			Data.Class = OldClass;
			Data.BlueprintAsset = FStringAssetReference(BPToUnload);
			ReplaceInnerData.Add(Data);
		}
	}

	// store off data for the initial blueprint so we can unload it (and reconstruct 
	// later to compare it with a second one)
	TArray<uint8> InitialLoadData;
	FObjectWriter(InitialBlueprint, InitialLoadData);

	// grab the name before we unload the blueprint
	FName const BlueprintName = InitialBlueprint->GetFName();
	// unload the blueprint so we can reload it (to catch any differences, now  
	// that all its dependencies should be loaded as well)

	//UNLOAD DEPENDENCIES, all circular dependencies will be loaded again 
	// unload the blueprint so we can reload it (to catch any differences, now  
	// that all its dependencies should be loaded as well)
	for (auto BPToUnloadWP : BlueprintDependencies)
	{
		if (auto BPToUnload = BPToUnloadWP.Get())
		{
			FBlueprintAutomationTestUtilities::UnloadBlueprint(BPToUnload);
		}
	}

	// this blueprint is now dead (will be destroyed next garbage-collection pass)
	UBlueprint* UnloadedBlueprint = InitialBlueprint;
	InitialBlueprint = NULL;

	// load the blueprint a second time; if the two separately loaded blueprints 
	// are different, then this one is most likely the choice one (it has all its 
	// dependencies loaded)

	UBlueprint* ReloadedBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));

	UPackage* TransientPackage = GetTransientPackage();
	FName ReconstructedName = MakeUniqueObjectName(TransientPackage, UBlueprint::StaticClass(), BlueprintName);
	// reconstruct the initial blueprint (using the serialized data from its initial load)
	EObjectFlags const StandardBlueprintFlags = RF_Public | RF_Standalone | RF_Transactional;
	InitialBlueprint = ConstructObject<UBlueprint>(UBlueprint::StaticClass(), TransientPackage, ReconstructedName, StandardBlueprintFlags | RF_Transient);
	FObjectReader(InitialBlueprint, InitialLoadData);
	{
		TMap<UObject*, UObject*> ClassRedirects;
		for (auto& Data : ReplaceInnerData)
		{
			UClass* OriginalClass = Data.Class.Get();
			UBlueprint* NewBlueprint = Cast<UBlueprint>(Data.BlueprintAsset.ResolveObject());
			UClass* NewClass = NewBlueprint ? *NewBlueprint->GeneratedClass : NULL;
			if (OriginalClass && NewClass)
			{
				ClassRedirects.Add(OriginalClass, NewClass);
			}
		}
		// REPLACE OLD DATA
		FArchiveReplaceObjectRef<UObject>(InitialBlueprint, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false);
		for (auto SubobjWP : InitialBlueprintSubobjects)
		{
			if (auto Subobj = SubobjWP.Get())
			{
				FArchiveReplaceObjectRef<UObject>(Subobj, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false);
			}
		}

		UPackage* AssetPackage = ReloadedBlueprint->GetOutermost();
		bool bHasUnsavedChanges = AssetPackage->IsDirty();
		FBlueprintEditorUtils::RefreshAllNodes(ReloadedBlueprint);
		AssetPackage->SetDirtyFlag(bHasUnsavedChanges);
	}

	// look for diffs between subsequent loads and log them as errors
	TArray<FDiffSingleResult> BlueprintDiffs;
	bool bDiffsFound = FBlueprintAutomationTestUtilities::DiffBlueprints(InitialBlueprint, ReloadedBlueprint, BlueprintDiffs);
	if (bDiffsFound)
	{
		FBlueprintAutomationTestUtilities::ResolveCircularDependencyDiffs(ReloadedBlueprint, BlueprintDiffs);
		// if there are still diffs after resolving any the could have been from unloaded circular dependencies
		if (BlueprintDiffs.Num() > 0)
		{
			AddError(FString::Printf(TEXT("Inconsistencies between subsequent blueprint loads for: '%s' (was a dependency not preloaded?)"), *BlueprintAssetPath));
		}
		else 
		{
			bDiffsFound = false;
		}
		
		// list all the differences (so as to help identify what dependency was missing)
		for (auto DiffIt(BlueprintDiffs.CreateIterator()); DiffIt; ++DiffIt)
		{
			// will be presented in the context of "what changed between the initial load and the second?"
			FString DiffDescription = DiffIt->ToolTip;
			if (DiffDescription != DiffIt->DisplayString)
			{
				DiffDescription = FString::Printf(TEXT("%s (%s)"), *DiffDescription, *DiffIt->DisplayString);
			}

			const UEdGraphNode* NodeFromPin = DiffIt->Pin1 ? Cast<const UEdGraphNode>(DiffIt->Pin1->GetOuter()) : NULL;
			const UEdGraphNode* Node = DiffIt->Node1 ? DiffIt->Node1 : NodeFromPin;
			const UEdGraph* Graph = Node ? Node->GetGraph() : NULL;
			const FString GraphName = Graph ? Graph->GetName() : FString(TEXT("Unknown Graph"));
			AddError(FString::Printf(TEXT("%s.%s differs between subsequent loads: %s"), *BlueprintName.ToString(), *GraphName, *DiffDescription));
		}
	}

	// At the close of this function, the FScopedBlueprintUnloader should prep 
	// for following tests by unloading any blueprint dependencies that were 
	// loaded for this one (should catch InitialBlueprint and ReloadedBlueprint) 
	// 
	// The FScopedBlueprintUnloader should also run garbage-collection after,
	// in hopes that the imports for this blueprint get destroyed so that they 
	// don't invalidate other tests that share the same dependencies
	return !bDiffsFound;
}