2020-09-01 08:38:42 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================================
CommandletPackageHelper . cpp : Utility class that provides tools to handle packages & source control operations .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
2020-09-14 13:54:21 -04:00
# include "PackageSourceControlHelper.h"
2020-09-01 08:38:42 -04:00
# include "Logging/LogMacros.h"
2021-06-10 18:04:34 -04:00
# include "UObject/Linker.h"
2020-09-01 08:38:42 -04:00
# include "UObject/Package.h"
# include "HAL/FileManager.h"
# include "HAL/PlatformFileManager.h"
2020-09-11 08:14:37 -04:00
# include "PackageTools.h"
2020-09-01 08:38:42 -04:00
# include "ISourceControlOperation.h"
# include "ISourceControlModule.h"
# include "ISourceControlState.h"
# include "ISourceControlProvider.h"
# include "SourceControlOperations.h"
2022-02-18 13:23:27 -05:00
# include "Misc/PackageName.h"
2020-09-01 08:38:42 -04:00
DEFINE_LOG_CATEGORY_STATIC ( LogCommandletPackageHelper , Log , All ) ;
2022-02-18 13:23:27 -05:00
namespace FPackageSourceControlHelperLog
{
void Error ( bool bErrorsAsWarnings , const FString & Msg )
{
if ( bErrorsAsWarnings )
{
UE_LOG ( LogCommandletPackageHelper , Warning , TEXT ( " %s " ) , * Msg ) ;
}
else
{
UE_LOG ( LogCommandletPackageHelper , Error , TEXT ( " %s " ) , * Msg ) ;
}
}
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : UseSourceControl ( ) const
2020-09-01 08:38:42 -04:00
{
2020-09-14 13:54:21 -04:00
return GetSourceControlProvider ( ) . IsEnabled ( ) ;
2020-09-01 08:38:42 -04:00
}
2020-09-14 13:54:21 -04:00
ISourceControlProvider & FPackageSourceControlHelper : : GetSourceControlProvider ( ) const
2020-09-11 08:14:37 -04:00
{
return ISourceControlModule : : Get ( ) . GetProvider ( ) ;
2020-09-01 08:38:42 -04:00
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : Delete ( const FString & PackageName ) const
2020-09-01 08:38:42 -04:00
{
2021-03-24 17:18:08 -04:00
TArray < FString > PackageNames = { PackageName } ;
return Delete ( PackageNames ) ;
}
2020-09-01 08:38:42 -04:00
2022-02-18 13:23:27 -05:00
bool FPackageSourceControlHelper : : Delete ( const TArray < FString > & PackageNames , bool bErrorsAsWarnings ) const
2021-03-24 17:18:08 -04:00
{
2022-02-18 13:23:27 -05:00
bool bSuccess = true ;
2021-03-24 17:18:08 -04:00
// Early out when not using source control
2020-09-01 08:38:42 -04:00
if ( ! UseSourceControl ( ) )
{
2021-03-24 17:18:08 -04:00
for ( const FString & PackageName : PackageNames )
2020-09-01 08:38:42 -04:00
{
2021-03-24 17:18:08 -04:00
FString Filename = SourceControlHelpers : : PackageFilename ( PackageName ) ;
2020-09-01 08:38:42 -04:00
2021-03-24 17:18:08 -04:00
if ( ! IPlatformFile : : GetPlatformPhysical ( ) . SetReadOnly ( * Filename , false ) | |
! IPlatformFile : : GetPlatformPhysical ( ) . DeleteFile ( * Filename ) )
{
2022-02-18 13:23:27 -05:00
bSuccess = false ;
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " Error deleting %s " ) , * Filename ) ) ;
if ( ! bErrorsAsWarnings )
{
return false ;
}
2021-03-24 17:18:08 -04:00
}
}
2022-02-18 13:23:27 -05:00
return bSuccess ;
2021-03-24 17:18:08 -04:00
}
TArray < FString > FilesToRevert ;
TArray < FString > FilesToDeleteFromDisk ;
TArray < FString > FilesToDeleteFromSCC ;
2022-02-18 13:23:27 -05:00
2021-03-24 17:18:08 -04:00
// First: get latest state from source control
2022-02-18 13:23:27 -05:00
TArray < FString > Filenames = SourceControlHelpers : : PackageFilenames ( PackageNames ) ;
2021-03-24 17:18:08 -04:00
TArray < FSourceControlStateRef > SourceControlStates ;
2022-02-18 13:23:27 -05:00
if ( GetSourceControlProvider ( ) . GetState ( Filenames , SourceControlStates , EStateCacheUsage : : ForceUpdate ) ! = ECommandResult : : Succeeded )
2021-03-24 17:18:08 -04:00
{
2022-02-18 13:23:27 -05:00
// Nothing we can do if SCCStates fail
FPackageSourceControlHelperLog : : Error ( false , TEXT ( " Could not get source control state for packages " ) ) ;
2021-03-24 17:18:08 -04:00
return false ;
}
2022-02-18 13:23:27 -05:00
2021-03-24 17:18:08 -04:00
for ( FSourceControlStateRef & SourceControlState : SourceControlStates )
{
const FString & Filename = SourceControlState - > GetFilename ( ) ;
UE_LOG ( LogCommandletPackageHelper , Verbose , TEXT ( " Deleting %s " ) , * Filename ) ;
if ( SourceControlState - > IsSourceControlled ( ) )
2020-09-01 08:38:42 -04:00
{
FString OtherCheckedOutUser ;
if ( SourceControlState - > IsCheckedOutOther ( & OtherCheckedOutUser ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s already checked out by %s, will not delete " ) , * Filename , * OtherCheckedOutUser ) ) ;
bSuccess = false ;
2020-09-01 08:38:42 -04:00
}
else if ( ! SourceControlState - > IsCurrent ( ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s (not at head revision), will not delete " ) , * Filename ) ) ;
bSuccess = false ;
2020-09-01 08:38:42 -04:00
}
else if ( SourceControlState - > IsAdded ( ) )
{
2021-03-24 17:18:08 -04:00
FilesToRevert . Add ( Filename ) ;
FilesToDeleteFromDisk . Add ( Filename ) ;
2020-09-01 08:38:42 -04:00
}
else
{
if ( SourceControlState - > IsCheckedOut ( ) )
{
2021-03-24 17:18:08 -04:00
FilesToRevert . Add ( Filename ) ;
2020-09-01 08:38:42 -04:00
}
2021-03-24 17:18:08 -04:00
FilesToDeleteFromSCC . Add ( Filename ) ;
2020-09-01 08:38:42 -04:00
}
}
else
{
2021-03-24 17:18:08 -04:00
FilesToDeleteFromDisk . Add ( Filename ) ;
2020-09-01 08:38:42 -04:00
}
}
2022-02-18 13:23:27 -05:00
if ( ! bSuccess & & ! bErrorsAsWarnings )
2021-03-24 17:18:08 -04:00
{
// Errors were found, we'll cancel everything
return false ;
}
// It's possible that not all files were in the source control cache, in which case we should still add them to the
// files to delete on disk.
if ( Filenames . Num ( ) ! = SourceControlStates . Num ( ) )
{
for ( FSourceControlStateRef & SourceControlState : SourceControlStates )
{
Filenames . Remove ( SourceControlState - > GetFilename ( ) ) ;
}
FilesToDeleteFromDisk . Append ( Filenames ) ;
}
// First, revert files from SCC
if ( FilesToRevert . Num ( ) > 0 )
{
if ( GetSourceControlProvider ( ) . Execute ( ISourceControlOperation : : Create < FRevert > ( ) , FilesToRevert ) ! = ECommandResult : : Succeeded )
{
2022-02-18 13:23:27 -05:00
bSuccess = false ;
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , TEXT ( " Error reverting packages from source control " ) ) ;
if ( ! bErrorsAsWarnings )
{
return false ;
}
2021-03-24 17:18:08 -04:00
}
}
// Then delete files from SCC
if ( FilesToDeleteFromSCC . Num ( ) > 0 )
{
if ( GetSourceControlProvider ( ) . Execute ( ISourceControlOperation : : Create < FDelete > ( ) , FilesToDeleteFromSCC ) ! = ECommandResult : : Succeeded )
{
2022-02-18 13:23:27 -05:00
bSuccess = false ;
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , TEXT ( " Error deleting packages from source control " ) ) ;
if ( ! bErrorsAsWarnings )
{
return false ;
}
2021-03-24 17:18:08 -04:00
}
}
// Then delete files on disk
for ( const FString & Filename : FilesToDeleteFromDisk )
{
if ( ! IFileManager : : Get ( ) . Delete ( * Filename , false , true ) )
{
2022-02-18 13:23:27 -05:00
bSuccess = false ;
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " Error deleting %s locally " ) , * Filename ) ) ;
if ( ! bErrorsAsWarnings )
{
return false ;
}
2021-03-24 17:18:08 -04:00
}
}
2022-02-18 13:23:27 -05:00
return bSuccess ;
2020-09-01 08:38:42 -04:00
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : Delete ( UPackage * Package ) const
2020-09-01 08:38:42 -04:00
{
2020-09-11 08:14:37 -04:00
TArray < UPackage * > Packages = { Package } ;
return Delete ( Packages ) ;
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : Delete ( const TArray < UPackage * > & Packages ) const
2020-09-11 08:14:37 -04:00
{
2020-09-11 12:13:50 -04:00
if ( Packages . IsEmpty ( ) )
{
return true ;
}
2022-02-11 15:38:51 -05:00
TArray < FString > PackageNames ;
PackageNames . Reserve ( Packages . Num ( ) ) ;
2020-09-11 08:14:37 -04:00
for ( UPackage * Package : Packages )
{
2022-02-11 15:38:51 -05:00
PackageNames . Add ( Package - > GetName ( ) ) ;
2021-06-09 10:40:40 -04:00
ResetLoaders ( Package ) ;
2020-09-11 08:14:37 -04:00
}
2022-02-11 15:38:51 -05:00
return Delete ( PackageNames ) ;
2020-09-01 08:38:42 -04:00
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : AddToSourceControl ( UPackage * Package ) const
2020-09-01 08:38:42 -04:00
{
if ( UseSourceControl ( ) )
{
FString PackageFilename = SourceControlHelpers : : PackageFilename ( Package ) ;
2022-01-19 13:35:45 -05:00
return AddToSourceControl ( { PackageFilename } ) ;
}
2020-09-01 08:38:42 -04:00
2022-01-19 13:35:45 -05:00
return true ;
}
2022-02-18 13:23:27 -05:00
bool FPackageSourceControlHelper : : AddToSourceControl ( const TArray < FString > & PackageNames , bool bErrorsAsWarnings ) const
2022-01-19 13:35:45 -05:00
{
if ( ! UseSourceControl ( ) )
{
return true ;
}
// Convert package names to package filenames
TArray < FString > PackageFilenames = SourceControlHelpers : : PackageFilenames ( PackageNames ) ;
// Two-pass checkout mechanism
TArray < FString > PackagesToAdd ;
PackagesToAdd . Reserve ( PackageFilenames . Num ( ) ) ;
bool bSuccess = true ;
TArray < FSourceControlStateRef > SourceControlStates ;
2022-02-18 13:23:27 -05:00
if ( GetSourceControlProvider ( ) . GetState ( PackageFilenames , SourceControlStates , EStateCacheUsage : : ForceUpdate ) ! = ECommandResult : : Succeeded )
2022-01-19 13:35:45 -05:00
{
2022-02-18 13:23:27 -05:00
// Nothing we can do if SCCStates fail
FPackageSourceControlHelperLog : : Error ( false , TEXT ( " Could not get source control state for packages " ) ) ;
2022-01-19 13:35:45 -05:00
return false ;
}
2022-02-18 13:23:27 -05:00
2022-01-19 13:35:45 -05:00
for ( FSourceControlStateRef & SourceControlState : SourceControlStates )
{
const FString & PackageFilename = SourceControlState - > GetFilename ( ) ;
FString OtherCheckedOutUser ;
if ( SourceControlState - > IsCheckedOutOther ( & OtherCheckedOutUser ) )
2020-09-01 08:38:42 -04:00
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s already checked out by %s, will not add " ) , * PackageFilename , * OtherCheckedOutUser ) ) ;
2022-01-19 13:35:45 -05:00
bSuccess = false ;
2020-09-01 08:38:42 -04:00
}
2022-01-19 13:35:45 -05:00
else if ( ! SourceControlState - > IsCurrent ( ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s (not at head revision), will not add " ) , * PackageFilename ) ) ;
2022-01-19 13:35:45 -05:00
bSuccess = false ;
}
else if ( SourceControlState - > IsAdded ( ) )
{
// Nothing to do
}
else if ( ! SourceControlState - > IsSourceControlled ( ) )
{
PackagesToAdd . Add ( PackageFilename ) ;
}
}
// Any error up to here will be an early out
2022-02-18 13:23:27 -05:00
if ( ! bSuccess & & ! bErrorsAsWarnings )
2022-01-19 13:35:45 -05:00
{
return false ;
}
if ( PackagesToAdd . Num ( ) )
{
2022-02-18 13:23:27 -05:00
if ( GetSourceControlProvider ( ) . Execute ( ISourceControlOperation : : Create < FMarkForAdd > ( ) , PackagesToAdd ) ! = ECommandResult : : Succeeded )
{
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , TEXT ( " Error adding packages to source control " ) ) ;
return false ;
}
2020-09-01 08:38:42 -04:00
}
2022-02-18 13:23:27 -05:00
return bSuccess ;
2020-09-01 08:38:42 -04:00
}
2020-09-14 13:54:21 -04:00
bool FPackageSourceControlHelper : : Checkout ( UPackage * Package ) const
2020-09-01 08:38:42 -04:00
{
2021-05-27 17:46:31 -04:00
return ! Package | | Checkout ( { Package - > GetName ( ) } ) ;
}
2020-09-01 08:38:42 -04:00
2022-02-18 13:23:27 -05:00
bool FPackageSourceControlHelper : : Checkout ( const TArray < FString > & PackageNames , bool bErrorsAsWarnings ) const
2021-05-27 17:46:31 -04:00
{
const bool bUseSourceControl = UseSourceControl ( ) ;
// Convert package names to package filenames
TArray < FString > PackageFilenames = SourceControlHelpers : : PackageFilenames ( PackageNames ) ;
// Two-pass checkout mechanism
TArray < FString > PackagesToCheckout ;
PackagesToCheckout . Reserve ( PackageFilenames . Num ( ) ) ;
2021-05-29 22:58:02 -04:00
bool bSuccess = true ;
2021-05-27 17:46:31 -04:00
// In the first pass, we will gather the packages to be checked out, or flag errors and return if we've found any
if ( bUseSourceControl )
{
TArray < FSourceControlStateRef > SourceControlStates ;
ECommandResult : : Type UpdateState = GetSourceControlProvider ( ) . GetState ( PackageFilenames , SourceControlStates , EStateCacheUsage : : ForceUpdate ) ;
if ( UpdateState ! = ECommandResult : : Succeeded )
2020-09-01 08:38:42 -04:00
{
2022-02-18 13:23:27 -05:00
// Nothing we can do if SCCStates fail
FPackageSourceControlHelperLog : : Error ( false , TEXT ( " Could not get source control state for packages " ) ) ;
2021-05-27 17:46:31 -04:00
return false ;
}
for ( FSourceControlStateRef & SourceControlState : SourceControlStates )
{
const FString & PackageFilename = SourceControlState - > GetFilename ( ) ;
2020-09-01 08:38:42 -04:00
FString OtherCheckedOutUser ;
if ( SourceControlState - > IsCheckedOutOther ( & OtherCheckedOutUser ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s already checked out by %s, will not checkout " ) , * PackageFilename , * OtherCheckedOutUser ) ) ;
2021-05-29 22:58:02 -04:00
bSuccess = false ;
2020-09-01 08:38:42 -04:00
}
else if ( ! SourceControlState - > IsCurrent ( ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s (not at head revision), will not checkout " ) , * PackageFilename ) ) ;
2021-05-29 22:58:02 -04:00
bSuccess = false ;
2020-09-01 08:38:42 -04:00
}
else if ( SourceControlState - > IsCheckedOut ( ) | | SourceControlState - > IsAdded ( ) )
{
2021-05-27 17:46:31 -04:00
// Nothing to do
2020-09-01 08:38:42 -04:00
}
else if ( SourceControlState - > IsSourceControlled ( ) )
{
2021-05-27 17:46:31 -04:00
PackagesToCheckout . Add ( PackageFilename ) ;
2020-09-01 08:38:42 -04:00
}
}
}
else
{
2021-05-27 17:46:31 -04:00
for ( const FString & PackageFilename : PackageFilenames )
2020-09-01 08:38:42 -04:00
{
2021-05-27 17:46:31 -04:00
if ( ! IPlatformFile : : GetPlatformPhysical ( ) . FileExists ( * PackageFilename ) )
2020-09-01 08:38:42 -04:00
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s cannot be checked out as it does not exist " ) , * PackageFilename ) ) ;
2021-05-29 22:58:02 -04:00
bSuccess = false ;
2021-05-27 17:46:31 -04:00
}
else if ( IPlatformFile : : GetPlatformPhysical ( ) . IsReadOnly ( * PackageFilename ) )
{
PackagesToCheckout . Add ( PackageFilename ) ;
2020-09-01 08:38:42 -04:00
}
}
}
2021-05-27 17:46:31 -04:00
// Any error up to here will be an early out
2022-02-18 13:23:27 -05:00
if ( ! bSuccess & & ! bErrorsAsWarnings )
2021-05-27 17:46:31 -04:00
{
return false ;
}
// In the second pass, we will perform the checkout operation
if ( PackagesToCheckout . Num ( ) = = 0 )
{
2022-02-18 13:23:27 -05:00
return bSuccess ;
2021-05-27 17:46:31 -04:00
}
else if ( bUseSourceControl )
{
2022-02-18 13:23:27 -05:00
if ( GetSourceControlProvider ( ) . Execute ( ISourceControlOperation : : Create < FCheckOut > ( ) , PackagesToCheckout ) ! = ECommandResult : : Succeeded )
{
// If operation didn't succeed. Get the list of invalid states when provided with a OutFailedPackages
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , TEXT ( " Error checking out packages from source control " ) ) ;
return false ;
}
2021-05-27 17:46:31 -04:00
}
else
{
int PackageIndex = 0 ;
for ( ; PackageIndex < PackagesToCheckout . Num ( ) ; + + PackageIndex )
{
if ( ! IPlatformFile : : GetPlatformPhysical ( ) . SetReadOnly ( * PackagesToCheckout [ PackageIndex ] , false ) )
{
2022-02-18 13:23:27 -05:00
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " Error setting %s writable " ) , * PackagesToCheckout [ PackageIndex ] ) ) ;
2021-05-29 22:58:02 -04:00
bSuccess = false ;
2021-05-27 17:46:31 -04:00
- - PackageIndex ;
break ;
}
}
// If a file couldn't be made writeable, put back the files to their original state
2022-02-18 13:23:27 -05:00
if ( ! bSuccess & & ! bErrorsAsWarnings )
2021-05-27 17:46:31 -04:00
{
for ( ; PackageIndex > = 0 ; - - PackageIndex )
{
IPlatformFile : : GetPlatformPhysical ( ) . SetReadOnly ( * PackagesToCheckout [ PackageIndex ] , true ) ;
}
}
}
2022-02-18 13:23:27 -05:00
return bSuccess ;
}
bool FPackageSourceControlHelper : : GetDesiredStatesForModification ( const TArray < FString > & PackageNames , TArray < FString > & OutPackagesToCheckout , TArray < FString > & OutPackagesToAdd , bool bErrorsAsWarnings ) const
{
// Convert package names to package filenames
TMap < FString , FString > PackageFilenamesToPackageName ;
TArray < FString > PackageFilenames ;
PackageFilenames . Reserve ( PackageNames . Num ( ) ) ;
for ( const FString & PackageName : PackageNames )
{
FString PackageFilename = SourceControlHelpers : : PackageFilename ( PackageName ) ;
PackageFilenamesToPackageName . Add ( PackageFilename , PackageName ) ;
PackageFilenames . Add ( PackageFilename ) ;
}
if ( ! UseSourceControl ( ) )
{
for ( const FString & PackageFilename : PackageFilenames )
{
if ( IPlatformFile : : GetPlatformPhysical ( ) . FileExists ( * PackageFilename ) & & IPlatformFile : : GetPlatformPhysical ( ) . IsReadOnly ( * PackageFilename ) )
{
OutPackagesToCheckout . Add ( PackageFilenamesToPackageName . FindChecked ( PackageFilename ) ) ;
}
}
return true ;
}
TArray < FSourceControlStateRef > SourceControlStates ;
if ( GetSourceControlProvider ( ) . GetState ( PackageFilenames , SourceControlStates , EStateCacheUsage : : ForceUpdate ) ! = ECommandResult : : Succeeded )
{
// Nothing we can do if SCCStates fail
FPackageSourceControlHelperLog : : Error ( false , TEXT ( " Could not get source control state for packages " ) ) ;
return false ;
}
bool bSuccess = true ;
for ( FSourceControlStateRef & SourceControlState : SourceControlStates )
{
const FString & PackageFilename = SourceControlState - > GetFilename ( ) ;
FString OtherCheckedOutUser ;
if ( SourceControlState - > IsCheckedOutOther ( & OtherCheckedOutUser ) )
{
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s already checked out by %s, will not checkout " ) , * PackageFilename , * OtherCheckedOutUser ) ) ;
bSuccess = false ;
}
else if ( ! SourceControlState - > IsCurrent ( ) )
{
FPackageSourceControlHelperLog : : Error ( bErrorsAsWarnings , FString : : Printf ( TEXT ( " File %s (not at head revision), will not checkout " ) , * PackageFilename ) ) ;
bSuccess = false ;
}
else if ( SourceControlState - > IsCheckedOut ( ) | | SourceControlState - > IsAdded ( ) )
{
// Nothing to do
}
else if ( SourceControlState - > IsSourceControlled ( ) )
{
OutPackagesToCheckout . Add ( PackageFilenamesToPackageName . FindChecked ( PackageFilename ) ) ;
}
else
{
OutPackagesToAdd . Add ( PackageFilenamesToPackageName . FindChecked ( PackageFilename ) ) ;
}
}
return bSuccess ;
2021-05-27 17:46:31 -04:00
}