script "Levure Framework Application Packager"
constant kStandaloneBuilderPlatforms = "MacOSX x86-32,MacOSX x86-64,MacOSX arm64,Windows,Windows x86-64,Linux,Linux x64,Linux armv6-hf,iOS,Android,Emscripten"
constant kPlatformsThatCantTest = "iOS,Android,Emscripten"
constant kNestedKeysDelimiter = ">"
local sAppA, sPassword, sBuildLogFile, sCallbackStacksA, sBuildStandaloneA
/* Enum used for command line packaging running|failed|cancelled|complete */
local sCommandLineStatus
on libraryStack
if the target is not me then pass libraryStack
end libraryStack
on releaseStack
if the target is not me then pass releaseStack
end releaseStack
private command log pMsg
local tError
open file sBuildLogFile for "utf8" text append
put the result into tError
if tError is empty then
write pMsg & cr to file sBuildLogFile
put the result into tError
end if
close file sBuildLogFile
return tError
end log
private command resetLogging
put levureBuildFolder() & "/build.log" into sBuildLogFile
delete file sBuildLogFile
end resetLogging
/**
Summary: Adds a message to the build log file.
Returns: nothing
*/
command packagerLog pMsg
log pMsg
return the result
end packagerLog
/**
Summary: Packages an application for configured platforms.
pStandaloneStackFilename: The filename of the standalone stack.
pBuildProfile: The build profile to use when packaging the application.
pSimulator: `ios` or `simulator`.
Description:
If `pSimulator` has a valid value then
Returns: Empty
*/
command packagerPackageApplication pStandaloneStackFilename, pBuildProfile, pSimulator
local tError, tBuildProfile, tBuildStandalone, tStandaloneBuilderPlatforms
local tSubFolder, tOutputFolder, tAppFolder, tPlatformFilter
local tHideConsole
put the hideConsoleWindows into tHideConsole
set the hideConsoleWindows to true
levureLoadAppConfig pBuildProfile
put levureAppGetConfig() into sAppA
put pBuildProfile into tBuildProfile
if pSimulator is "ios" then
put false into tBuildStandalone
put "ios" into tStandaloneBuilderPlatforms
else if pSimulator is "android" then
put false into tBuildStandalone
put "android" into tStandaloneBuilderPlatforms
else
put true into tBuildStandalone
put kStandaloneBuilderPlatforms into tStandaloneBuilderPlatforms
end if
put sAppA["build profiles"][pBuildProfile]["platform filter"] into tPlatformFilter
_performPackagingPrechecks pStandaloneStackFilename, pBuildProfile
put the result into tError
resetLogging
log "[" & the system date & "] Packaging app using profile" && pBuildProfile
if tError is empty then
local tTempFolder, tCopyFilesTempFolder
put levureAppFolder() into tAppFolder
put the temporary folder into tTempFolder
put tTempFolder & "/levure-copy-files-" & the milliseconds into tCopyFilesTempFolder
if tBuildStandalone then
put levureAppGet("version") & "-" & levureAppGet("build") into tSubFolder
put levureBuildFolder() & "/" & pBuildProfile & "/" & tSubFolder into tOutputFolder
log "Output folder:" && tOutputFolder
if there is a folder tOutputFolder then
_deleteFolder tOutputFolder
if the result is not empty then
put "error deleting existing package folder:" && the result into tError
end if
end if
else
put tCopyFilesTempFolder into tOutputFolder
end if
end if
if tError is empty then
local tPlatform, tArchitecture, tBuildPlatform
local tAppA, tWorkingOutputFolder, tExecutableOutputFolder
local tCertName
repeat for each item tBuildPlatform in tStandaloneBuilderPlatforms
put _buildPlatformToLevurePlatform(tBuildPlatform) into tPlatform
put _buildPlatformToArchitecture(tBuildPlatform) into tArchitecture
if not the cRevStandaloneSettings[tBuildPlatform] of stack pStandaloneStackFilename then next repeat
if tPlatform is "macos" and the platform is not "macos" then next repeat
if not _shouldBuildForPlatform(tPlatform, tPlatformFilter) then next repeat
# Create folder for copy files
log "packaging for platform:" && tPlatform
_determineSigningCertificate pBuildProfile, tPlatform, sAppA
# Create version of app array that will be modfiied and stored with package
put sAppA into tAppA
if tError is empty then
packagerCreateAllFoldersInPath tCopyFilesTempFolder, tTempFolder
put _errorMsg("creating copy files temp folder [" & tCopyFilesTempFolder & "] [" & tTempFolder & "]", the result) into tError
end if
# Send before filenames are made relative. Gives helpers a chance to modify
# tAppA list of assets.
if tError is empty then
_dispatchFinalizePackagedAssets pBuildProfile, tPlatform, tAppA, tAppFolder
end if
# Copy over all files
if tError is empty then
_prepareAppFilesForStandaloneBuilding pStandaloneStackFilename, pBuildProfile, tPlatform, tArchitecture, tAppA, tCopyFilesTempFolder
put the result into tError
end if
# Process target specific instructions in "copy files"
if tError is empty then
_copyFiles pBuildProfile, tPlatform, tArchitecture, tAppA, tAppFolder, tCopyFilesTempFolder
put the result into tError
end if
# Process instructions for all platform
if tError is empty then
_copyFiles pBuildProfile, "all platforms", tArchitecture, tAppA, tAppFolder, tCopyFilesTempFolder
put the result into tError
end if
# Prune and move in app stack
if tError is empty then
local tSourceStackFilename, tTargetStackFilename, isReloaded
_pruneAppArray tAppA, pBuildProfile, tPlatform
log "tAppA:" && _printArray(tAppA,,true)
set the itemdelimiter to "/"
# Copy over framework file. Mobile standalone builder won't copy it. Desktop will.
put levureFrameworkFilename() into tSourceStackFilename
put tCopyFilesTempFolder & "/" & the last item of tSourceStackFilename into tTargetStackFilename
_fileCopyFile tSourceStackFilename, tTargetStackFilename
put _errorMsg("copying file [" & tSourceStackFilename & "] to [" & tTargetStackFilename & "]", the result) into tError
if tError is empty then
put levureAppStackFilename() into tSourceStackFilename
put tCopyFilesTempFolder & "/" & the last item of tSourceStackFilename into tTargetStackFilename
log "Source stack filename:" && tSourceStackFilename
log "App stack filename:" && tTargetStackFilename
_fileCopyFile tSourceStackFilename, tTargetStackFilename
put _errorMsg("copying file [" & tSourceStackFilename & "] to [" & tTargetStackFilename & "]", the result) into tError
end if
if tError is empty then
delete stack tSourceStackFilename
set the scriptonly of stack tTargetStackFilename to false
set the uAppA of stack tTargetStackFilename to tAppA
_setPassword tTargetStackFilename, sPassword
save stack tTargetStackFilename with newest format
put _errorMsg("saving app stack", the result) into tError
put the result into tError
delete stack tTargetStackFilename
put there is a stack tSourceStackFilename into isReloaded
end if
set the itemdelimiter to ","
end if
# Now build standalone for target platform
if tError is empty then
local tCopyFiles
put _createCopyFilesListFromFolder(tCopyFilesTempFolder) into tCopyFiles
log "Build: copy files list for platform" && tPlatform & ":" & cr & tCopyFiles
if tBuildStandalone then
packagerBuildStandalones pStandaloneStackFilename, tBuildPlatform, pBuildProfile, \
tSubFolder, tCopyFiles, tAppA
put the result into tError
else
set the cRevStandaloneSettings["files"] of stack pStandaloneStackFilename to tCopyFiles
end if
end if
if tError is empty then
# Refresh after having unloaded for standalone builder
levureLoadAppConfig pBuildProfile
_dispatchFinalizeBuildForPlatform pBuildProfile, tPlatform, tAppA, tAppFolder, tOutputFolder & "/" & tPlatform
# dispatch and then sign macos bundle
# Use original sAppA as it has certificate information not found in pruned version.
if tPlatform is "macos" then
_signMacOSApplication pBuildProfile, sAppA, tOutputFolder & "/" & tPlatform
put the result into tError
if tError is empty then
# PKG can be uploaded to the Mac App Store or used with device management systems such as JAMF
log "Creating .pkg for macOS"
_createMacOsPkg pBuildProfile, sAppA, tOutputFolder, tOutputFolder & "/" & tPlatform
put the result into tError
end if
end if
end if
# If not building standalone then leave folder in place. We are building for a simulator.
if tBuildStandalone and there is a folder tCopyFilesTempFolder then
local tDeleteError
_deleteFolder tCopyFilesTempFolder
put _errorMsg("Error cleaning up copy files temp folder [" & tCopyFilesTempFolder & "]", the result) into tDeleteError
if tDeleteError is not empty then
log tDeleteError
end if
end if
if tError is not empty then
exit repeat
end if
end repeat
end if
# Process "package folder" key in "copy files"
if tBuildStandalone then
if tError is empty then
_copyFiles pBuildProfile, "package folder", tArchitecture, sAppA, tAppFolder, tOutputFolder
put the result into tError
end if
if tError is empty then
_dispatchPackagingComplete pBuildProfile, sAppA, tOutputFolder
end if
end if
# Remove callback stacks from memory
local tCallbackStacks
repeat for each key tFilename in sCallbackStacksA
if there is a stack tFilename then
delete stack tFilename
end if
end repeat
put empty into sCallbackStacksA
if tError is "cancel" then
log "Packaging canceled"
put "cancelled" into sCommandLineStatus
else if tError is not empty then
log "Done packaging application with error:" && tError
if sCommandLineStatus is "running" then
put "failed" into sCommandLineStatus
write "Done packaging application with error:" && tError & return \
to stderr
else
answer error tError
end if
else
log "Done packaging application"
put "complete" into sCommandLineStatus
end if
send "packagerDidFinishPackagingApplication pStandaloneStackFilename, tBuildProfile, pSimulator" to stack pStandaloneStackFilename in 10 milliseconds
set the hideConsoleWindows to tHideConsole
end packagerPackageApplication
private command _determineSigningCertificate pBuildProfile, pPlatform, @xAppA
local tKey, tCert
if pPlatform is "macos" then
put "name" into tKey
else
put "filename" into tKey
end if
put xAppA["build profiles"][pBuildProfile]["certificates"][pPlatform][tKey] into tCert
if tCert is empty then
put xAppA["build profiles"]["all profiles"]["certificates"][pPlatform][tKey] into tCert
end if
if tCert is not empty then
if pBuildProfile is "mac app store" and pPlatform is "macos" then
put "3rd Party Mac Developer Application:" && tCert into xAppA[pPlatform && "signing certificate"]
put "3rd Party Mac Developer Installer:" && tCert into xAppA[pPlatform && "installer signing certificate"]
else if pPlatform is "macos" then
put "Developer ID Application:" && tCert into xAppA[pPlatform && "signing certificate"]
put "Developer ID Installer:" && tCert into xAppA[pPlatform && "installer signing certificate"]
else if pPlatform is "windows" then
put tCert into xAppA[pPlatform && "signing certificate"]
end if
end if
end _determineSigningCertificate
private command _setPassword pStackName, pPassword
if pPassword is not empty then
if revLicenseType() is not "community" then
log " Encrypting stack" && pStackName
set the password of stack pStackName to pPassword
else
log " Skipping encryption in Community edition" && pStackName
end if
end if
return empty
end _setPassword
private command _prepareAppFilesForStandaloneBuilding pStandaloneStackFilename, pBuildProfile, pPlatform, pArchitecture, @xAppA, pOutputFolder
local tError
# Behaviors, Libraries, Frontscripts, Backscripts
# Create single stack for each and move all as substacks of single stack.
# For libraries, frontscripts, and backscripts store a custom property with order to load them in.
if tError is empty then
_packageAssetStacks pBuildProfile, xAppA, pPlatform, pOutputFolder
put the result into tError
end if
# Extensions
# Move straight across
if tError is empty then
_packageExtensions pBuildProfile, xAppA, pPlatform, pOutputFolder, the cRevStandaloneSettings["extensions"] of stack pStandaloneStackFilename
put the result into tError
end if
# UI
# Move straight across.
# Convert script only stacks to regular stacks
if tError is empty then
_packageUI pBuildProfile, pPlatform, xAppA, pOutputFolder
put the result into tError
end if
# Helpers
# Move straight over
# Convert any script only stacks to stacks
# Remove helper.yml file.
if tError is empty then
_packageHelpers pBuildProfile, xAppA, pPlatform, pArchitecture, pOutputFolder, pOutputFolder
put the result into tError
end if
# Files
# Move straight over
if tError is empty then
_packageFiles pBuildProfile, pPlatform, xAppA, pOutputFolder
put the result into tError
end if
return tError for error
end _prepareAppFilesForStandaloneBuilding
private function _createCopyFilesListFromFolder pFolder
local tFiles, tFolders
put _fileListing(pFolder) into tFiles
filter tFiles without ".*" # .DS_Store
if tFiles is not empty then put cr after tFiles
put _folderListing(pFolder) into tFolders
repeat for each line tFolder in tFolders
put tFolder & "/*" & cr after tFiles
end repeat
delete the last char of tFiles
return tFiles
end _createCopyFilesListFromFolder
private command _pruneAppArray @xAppA, pBuildProfile, pPlatform
local tKeysToDelete
local tFilename
local tCallbackStacks
delete local xAppA["password"]
delete local xAppA["build folder"]
delete local xAppA["encrypt stacks"]
delete local xAppA["build profiles"]
delete local xAppA["creator code"]
delete local xAppA["externals packages to verify"]
delete local xAppA["externals to load"]
delete local xAppA["packager callbacks stackfiles"]
switch pPlatform
case "windows"
put "macos,linux,ios,android" into tKeysToDelete
break
case "macos"
put "windows,linux,ios,android" into tKeysToDelete
break
case "linux"
put "windows,macos,ios,android" into tKeysToDelete
break
case "android"
put "linux,windows,macos,ios" into tKeysToDelete
break
case "ios"
put "linux,windows,macos,android" into tKeysToDelete
break
end switch
repeat for each item tKey in tKeysToDelete
delete local xAppA["preferences filename"]["user"][tKey]
delete local xAppA["preferences filename"]["shared"][tKey]
end repeat
/* Delete callback stackfiles that will be resolved at runtime */
repeat for each key tKey in xAppA["registered components"]["files"]
delete local xAppA["registered components"]["files"][tKey]["callback stackfile"]
end repeat
return empty for error
end _pruneAppArray
private function _toArray
local i, tA
repeat with i = 1 to the paramcount
put param(i+1) into tA[param(i)]
add 1 to i
end repeat
return tA
end _toArray
private function _packagerCallbackStacks pAppA, pBuildProfile
local tCallbackStacks
put sAppA["packager callbacks stackfiles"] into tCallbackStacks
if _packagerCallbacksStackFile(pAppA, pBuildProfile) is not empty then
put _packagerCallbacksStackFile(pAppA, pBuildProfile) into line (the number of lines of tCallbackStacks + 1) of tCallbackStacks
end if
return tCallbackStacks
end _packagerCallbackStacks
private command _dispatchFinalizeBuildForPlatform pBuildProfile, pPlatform, @xAppA, pAppFolder, pOutputFolder
local tCallbackStacks, tFilename
# Send callbacks
put _packagerCallbackStacks(xAppA, pBuildProfile) into tCallbackStacks
repeat for each line tFilename in tCallbackStacks
log "Dispatching finalizePackageForPlatform to stack" && tFilename
dispatch "finalizePackageForPlatform" to stack tFilename with pBuildProfile, pPlatform, xAppA, pAppFolder, pOutputFolder
put "" into sCallbackStacksA[ tFilename ]
end repeat
end _dispatchFinalizeBuildForPlatform
private command _dispatchFinalizePackagedAssets pBuildProfile, pPlatform, @xAppA, pAppFolder
local tCallbackStacks, tFilename
# Send callbacks
put _packagerCallbackStacks(xAppA, pBuildProfile) into tCallbackStacks
repeat for each line tFilename in tCallbackStacks
log "Dispatching finalizePackagedAssets to stack" && tFilename
dispatch "finalizePackagedAssets" to stack tFilename with pBuildProfile, pPlatform, xAppA, pAppFolder
put "" into sCallbackStacksA[ tFilename ]
end repeat
end _dispatchFinalizePackagedAssets
private command _dispatchPackagingComplete pBuildProfile, @xAppA, pOutputFolder
local tCallbackStacks, tFilename
# Send callbacks
put _packagerCallbackStacks(xAppA, pBuildProfile) into tCallbackStacks
repeat for each line tFilename in tCallbackStacks
log "Dispatching packagingComplete to stack" && tFilename
dispatch "packagingComplete" to stack tFilename with pBuildProfile, pOutputFolder, xAppA
end repeat
end _dispatchPackagingComplete
command packagerBuildStandaloneForTesting pStandaloneStackFilename
local tError, tOutputFolder, tHideConsole
local tBuildProfile = "test"
put the hideConsoleWindows into tHideConsole
set the hideConsoleWindows to true
resetLogging
log "[" & the system date & "] Packaging app using profile test"
put levureBuildFolder() & "/" & tBuildProfile into tOutputFolder
log "Output folder:" && tOutputFolder
if there is a folder tOutputFolder then
_deleteFolder tOutputFolder
if the result is not empty then
put "unable to delete output folder for standalone testing:" && the result into tError
end if
end if
if tError is empty then
repeat for each item tBuildPlatform in kStandaloneBuilderPlatforms
if tBuildPlatform is among the items of kPlatformsThatCantTest \
OR not the cRevStandaloneSettings[tBuildPlatform] of stack pStandaloneStackFilename then next repeat
packagerBuildStandalones pStandaloneStackFilename, tBuildPlatform, tBuildProfile, empty, sAppA
put the result into tError
if tError is empty then
# Refresh after having unloaded for standalone builder
levureLoadAppConfig
end if
if tError is not empty then
exit repeat
end if
end repeat
end if
if tError is not empty then
log "Done building standalone for testing with error:" && tError
answer error tError
else
log "Done building standalone for testing"
end if
send "packagerDidFinishBuildingStandaloneForTesting" to stack pStandaloneStackFilename in 10 milliseconds
set the hideConsoleWindows to tHideConsole
end packagerBuildStandaloneForTesting
command packagerBuildStandalones pStandaloneStackFilename, pBuildForPlatform, pBuildProfile, pSubfolder, pCopyFiles, pPrunedAppA
local tError
local buildForDistribution, tTempBuildFolder, tSourceFolder, tOutputFolder
if the keys of sAppA is empty then
put levureAppGetConfig() into sAppA
end if
put levureAppFolder() into sBuildStandaloneA["app folder"]
put pBuildProfile into sBuildStandaloneA["build profile"]
put _buildPlatformToLevurePlatform(pBuildForPlatform) into sBuildStandaloneA["target platform"]
put _buildPlatformToArchitecture(pBuildForPlatform) into sBuildStandaloneA["architecture"]
put _packagerCallbackStacks(sAppA, pBuildProfile) into sBuildStandaloneA["callback stacks"]
put pPrunedAppA into sBuildStandaloneA["app array"]
put tolower(pBuildProfile) into pBuildProfile
_validateBuildProfile pBuildProfile
put the result into tError
log "Build: Building standalones for profile:" && pBuildProfile
put word 1 to -1 of line 1 of pSubfolder into pSubfolder
set the itemdelimiter to "/"
put pBuildProfile is not "test" into buildForDistribution
put levureBuildFolder() into tOutputFolder
local tAppPath, tStandaloneFolder
put levureAppFolder() into tAppPath
put levureStandaloneFolder() into tStandaloneFolder
# tFrameworkFilename is to work around issue where framework isn't being hooked
# up as the behavior at the end of handler when reloading standalone stack and putting
# it in use. See code at end of handler that uses tFrameworkFilename.
local tFrameworkFilename
put the filename of stack "levureFramework" into tFrameworkFilename
put the effective filename of stack pStandaloneStackFilename into tSourceFolder
delete the last item of tSourceFolder
if tError is empty then
if tOutputFolder is empty then
put "no build folder has been set" into tError
end if
if buildForDistribution and pSubfolder is empty then
put "subfolder cannot be empty" into tError
end if
if levureAppGet("version") is empty then
put "version has not been set" into tError
end if
end if
if tError is empty then
if there is not a folder tOutputFolder then
create folder tOutputFolder
put _errorMsg("creating folder [" & tOutputFolder & "]", the result) into tError
end if
end if
if tError is empty then
if buildForDistribution then
put "/" & pBuildProfile after tOutputFolder
put "/" & pSubfolder after tOutputFolder
else
put "/" & pBuildProfile after tOutputFolder
end if
if there is not a folder tOutputFolder then
packagerCreateAllFoldersInPath tOutputFolder, levureBuildFolder()
put _errorMsg("creating folder [" & tOutputFolder & "]", the result) into tError
end if
log "Build: Output folder:" && tOutputFolder
end if
if tError is empty then
log "Build: Update standalone version info"
_updateStandaloneVersionInfo pStandaloneStackFilename
put the result into tError
end if
if tError is empty then
log "Build: Saving stack using IDE call"
revIDESaveStack the long id of stack pStandaloneStackFilename
put _errorMsg("calling revIDESaveStack [" & pStandaloneStackFilename & "]", the result) into tError
end if
# Build startup script before unloading framework stack
if tError is empty then
local tStackFiles, tLineNo
set the itemdelimiter to ","
put the stackfiles of stack pStandaloneStackFilename into tStackFiles
put lineoffset("levureFramework", tStackFiles) into tLineNo
if tLineNo is 0 then
put "levureFramework is not assigned to the stackfiles of the standalone stack" into tError
end if
end if
if tError is empty then
local tFrameworkPath, tScript
put item 2 to -1 of line tLineNo of tStackFiles into tFrameworkPath
put _resolveRelativeFilenameReference(tFrameworkPath, tSourceFolder) into tFrameworkPath
put _makeRelativePath(tFrameworkPath, tOutputFolder) into tFrameworkPath
put levureAppFolder() into tAppPath
if buildForDistribution then
replace tStandaloneFolder with empty in tAppPath
if tAppPath begins with "/" then
delete char 1 of tAppPath
end if
else
put _makeRelativePath(tAppPath, tOutputFolder) into tAppPath
end if
put _generateStartupScript(tFrameworkPath, tAppPath, buildForDistribution) into tScript
if the script of stack pStandaloneStackFilename is not empty then
put cr & cr & the script of stack pStandaloneStackFilename after tScript
end if
log "Build: Startup script:" && tScript
end if
# Create new stack to build standalone with
if tError is empty then
local tTempStandaloneStack
put tSourceFolder & "/levureBuildStandaloneTemp_" & the milliseconds & ".livecode" into tTempStandaloneStack
log "Build: Temp stack:" && tTempStandaloneStack
# Stop using first. See: http://quality.livecode.com/show_bug.cgi?id=19246
stop using stack pStandaloneStackFilename
_unloadStackAndStackFiles pStandaloneStackFilename
_fileCopyFile pStandaloneStackFilename, tTempStandaloneStack
put the result into tError
end if
if tError is empty then
# If this wait isn't here then the IDE reloads pStandaloneStackFilename when tTempStandaloneStack
# is accessed. Many precious moments of my life have been lost trying to figure out why. My best
# guess is that an idemessage is being sent around and some component is holding onto the stack somehow.
# This `wait` message is me tapping out.
wait 1 second with messages
if not buildForDistribution then
set the stackfiles of stack tTempStandaloneStack to empty
set the behavior of stack tTempStandaloneStack to empty
end if
set the uBuildProfile of stack tTempStandaloneStack to pBuildProfile
set the script of stack tTempStandaloneStack to tScript
if pBuildProfile is "test" then
local tScriptLibraries
set the wholematches to true
put the cRevStandaloneSettings["scriptLibraries"] of stack tTempStandaloneStack into tScriptLibraries
if "Remote Debugger" is not among the lines of tScriptLibraries then
put "Remote Debugger" into line (the number of lines of tScriptLibraries + 1) of tScriptLibraries
set the cRevStandaloneSettings["scriptLibraries"] of stack tTempStandaloneStack to tScriptLibraries
end if
end if
# Only build for target platform
repeat for each item tPlatform in kStandaloneBuilderPlatforms
set the cRevStandaloneSettings[tPlatform] of stack tTempStandaloneStack to tPlatform is pBuildForPlatform
end repeat
set the cRevStandaloneSettings["files"] of stack tTempStandaloneStack to pCopyFiles
log "Build: Saving temporary stack with IDE call"
revIDESaveStack the long id of stack tTempStandaloneStack
put the result into tError
end if
## Now build standalones
if tError is empty then
local tStack
put the short name of stack tTempStandaloneStack into tStack
put specialFolderPath("temporary") into tTempBuildFolder
put "/revstandalone" & the milliseconds after tTempBuildFolder
create folder tTempBuildFolder
put _errorMsg("creating folder" && tTempBuildFolder, the result) into tError
end if
if tError is empty then
local tStandaloneError
# Assign defaultBuildFolder so that IDE doesn't ask you to save stack
set the cRevStandaloneSettings["defaultBuildFolder"] of stack tTempStandaloneStack to tTempBuildFolder
try
log "Build: revSaveAsStandalone"
# Intercept standaloneSaved and dispatch custom message.
local tFrontScriptFilename
put the filename of me into tFrontScriptFilename
set the itemDelimiter to "/"
put "packager_frontscript.livecodescript" into the last item of tFrontScriptFilename
insert script of stack tFrontScriptFilename into front
# This will trigger a "purge stack" error for the levureFramework stack. The stack in question resides
# in the tmp stack where LiveCode builds the standalone. My guess is that the
# revSaveAsStandalone handler is not unloading the stackfiles stacks that the engine automatically loads
# when loading a stack. See _unloadStackAndStackFiles in packager.livecodescript and look at adding same logic
# to the LiveCode revSaveAsStandalone handler.
dispatch "revSaveAsStandalone" to stack "revSaveAsStandalone" with tStack, tTempBuildFolder,, true
put the result into tStandaloneError
catch e
put e into tStandaloneError
finally
remove script of stack tFrontScriptFilename from front
put empty into sBuildStandaloneA
if tStandaloneError is not empty then
log "revSaveAsStandalone error:" && tStandaloneError
put "cancel" into tError
end if
end try
end if
## Finally, copy standalones to proper folder
if tError is empty then
log "Build: copy standalones to proper folder"
_copyExecutablesAndSupportingFilesToFolder _buildPlatformToLevurePlatform(pBuildForPlatform), buildForDistribution, tTempBuildFolder, tOutputFolder
put the result into tError
end if
if there is a folder tTempBuildFolder then
_deleteFolder tTempBuildFolder
end if
if there is a file tTempStandaloneStack then
stop using stack tTempStandaloneStack
_unloadStackAndStackFiles tTempStandaloneStack
delete file tTempStandaloneStack
end if
# reload
# framework stack may be the tmp folder version. Just unload as it will be reloaded
# when loading the original standalone stack back into memory. Once LiveCode uploads
# standalone builder with update that unloads stackFiles during standalone builder then
# this code can be removed.
if there is a stack "levureFramework" then
delete stack "levureFramework"
end if
# Problem: Reopening this stack isn't loading the behavior of the stack (levureFramework).
# back into memory. Why? Outside of this handler the engine always loads the behavior script.
# Workaround: Grab the filename of stack at beginning of handler. Specifically reassign the
# behavior before putting the standalone stack back in use.
go stack pStandaloneStackFilename
set the behavior of stack pStandaloneStackFilename to empty
set the behavior of stack pStandaloneStackFilename to the long id of stack tFrameworkFilename
start using stack pStandaloneStackFilename
# If this wait isn't here then the IDE gets confused between pStandaloneStackFilename and tTempStandaloneStack
# This wait message is me giving up on trying to figure out what is going on.
wait 1 second with messages
return tError
end packagerBuildStandalones
command packagerStandaloneSaved pFolderSavedIn
local tStack, tError
if the last char of pFolderSavedIn is "/" then delete the last char of pFolderSavedIn
if sBuildStandaloneA["target platform"] is "macos" then
local tAppBundle
# Use .app bundle path
put folders(pFolderSavedIn) into tAppBundle
filter tAppBundle with "*.app"
put pFolderSavedIn & "/" & tAppBundle into pFolderSavedIn
_copyFiles sBuildStandaloneA["build profile"], "macos resources", sBuildStandaloneA["architecture"], sAppA, \
sBuildStandaloneA["app folder"], pFolderSavedIn & "/Contents/Resources"
put the result into tError
end if
if tError is empty then
repeat for each line tStack in sBuildStandaloneA["callback stacks"]
dispatch "finalizeStandaloneForPlatform" to stack tStack \
with sBuildStandaloneA["build profile"], sBuildStandaloneA["target platform"], \
sBuildStandaloneA["app array"], sBuildStandaloneA["app folder"], pFolderSavedIn
end repeat
end if
if tError is not empty then
answer error "Unable to copy `macos resources` files:" && tError & "."
end if
end packagerStandaloneSaved
command packagerMobileStandaloneSaved pTargetType, pAppBundle
local tStack, tError
if the last char of pAppBundle is "/" then delete the last char of pAppBundle
if sBuildStandaloneA["target platform"] is "macos" then
_copyFiles sBuildStandaloneA["build profile"], "macos resources", sBuildStandaloneA["architecture"], sAppA, \
sBuildStandaloneA["app folder"], pAppBundle & "/Contents/Resources"
put the result into tError
end if
if tError is empty then
repeat for each line tStack in sBuildStandaloneA["callback stacks"]
dispatch "finalizeStandaloneForPlatform" to stack tStack \
with sBuildStandaloneA["build profile"], sBuildStandaloneA["target platform"], \
sBuildStandaloneA["app array"], sBuildStandaloneA["app folder"], pAppBundle
end repeat
end if
if tError is not empty then
answer error "Unable to copy `macos resources` files:" && tError & "."
end if
end packagerMobileStandaloneSaved
private function _packagerCallbacksStackFile pAppA, pBuildProfile
local tFilename
put pAppA["build profiles"][pBuildProfile]["packager callbacks stackfile"] into tFilename
if tFilename is empty then
put pAppA["build profiles"]["all profiles"]["packager callbacks stackfile"] into tFilename
end if
if tFilename is not empty then
put _resolveRelativeFilenameReference(tFilename, levureAppFolder()) into tFilename
return tFilename for value
else
return empty for value
end if
end _packagerCallbacksStackFile
private function _buildPlatformToLevurePlatform pBuildPlatform
switch pBuildPlatform
case "Windows"
case "Windows x86-64"
return "windows"
case "MacOSX x86-32"
case "MacOSX x86-64"
case "MacOSX arm64"
return "macos"
case "Linux"
case "Linux x64"
case "Linux armv6-hf"
return "linux"
case "iOS"
return "ios"
case "Android"
return "android"
case "Emscripten"
return "emscripten"
default
throw param(0) && "invalid build platform:" && pBuildPlatform
end switch
end _buildPlatformToLevurePlatform
private function _buildPlatformToArchitecture pBuildPlatform
if pBuildPlatform is among the items of "Windows,Linux,MacOSX x86-32" then
return 32
else
return 64
end if
end _buildPlatformToArchitecture
private function _errorMsg pPrefix, pError
if pError is not empty then
if pError is not empty AND pError is not "cancel" AND pError is not "cancelled" then
return pPrefix && "(" & pError & ")"
end if
end if
return empty
end _errorMsg
/**
Summary: Process the "copy files" section of the application configuration.
pBuildProfile: The target build profile.
pTargetPlatform: The platform to target when copying files.
pArchitecture: 64 or 32.
pAppA: The app config array to process.
pRootFolder: Root folder to use for resolving relative paths.
pDestinationFolder: Location where files are being copied to.
Returns: Error message
*/
private command _copyFiles pBuildProfile, pTargetPlatform, pArchitecture, pAppA, pRootFolder, pDestinationFolder
local tError
local i, tProfile, tFilename, tFiledata, tFoldersA
if pTargetPlatform is not among the items of "package folder,all platforms,macos,macos resources,windows,linux,ios,ios resources,android,emscripten" then \
throw "invalid target platform for copy files action:" && pTargetPlatform
put tolower(pBuildProfile) into pBuildProfile
set the itemdelimiter to "/"
if tError is empty then
local tDestination, tFilesA, tKey, tIndex, tMaintainFilenameFolderStructure
log "Copying files for" && pTargetPlatform && pArchitecture
repeat for each item tProfile in "all profiles/" & pBuildProfile
# Build list of files to copy over. Start with known files for target platform.
put pAppA["build profiles"][tProfile]["copy files"][pTargetPlatform] into tFilesA
put the number of elements of tFilesA into tIndex
# Now add in any "copy files" registered components that target this platform
repeat for each key tKey in pAppA["registered components"]["copy files"]
if pAppA["registered components"]["copy files"][tKey]["target platform"] is pTargetPlatform then
repeat with i = 1 to the number of elements of pAppA["build profiles"][tProfile]["copy files"][tKey]
add 1 to tIndex
put pAppA["build profiles"][tProfile]["copy files"][tKey][i] into tFilesA[tIndex]
put pAppA["registered components"]["copy files"][tKey]["callback stackfile"] into tFilesA[tIndex]["callback stackfile"]
put pAppA["registered components"]["copy files"][tKey]["destination"] into tFilesA[tIndex]["destination"]
end repeat
end if
end repeat
repeat with i = 1 to the number of elements of tFilesA
# Don't process an empty entry
if tFilesA[i]["filename"] is empty then next repeat
if not _doesStringPassFilter(pBuildProfile, tFilesA[i]["build profiles filter"]) then next repeat
put not (tFilesA[i]["filename"] begins with "../") into tMaintainFilenameFolderStructure
# Figure out destination
put tFilesA[i]["destination"] into tDestination
if tDestination is empty then
# If filename is not relative and contains a folder reference then use same folder structure when moving to destination.
if tMaintainFilenameFolderStructure and the number of items of tFilesA[i]["filename"] > 1 then
put pDestinationFolder & "/" & item 1 to -2 of tFilesA[i]["filename"] into tDestination
else
put pDestinationFolder into tDestination
end if
else
put pDestinationFolder & "/" & _normalizeRelativeFilename(tDestination) into tDestination
# tDestination may have been `./` or `/`.
if the last char of tDestination is "/" then
delete the last char of tDestination
end if
end if
packagerCreateAllFoldersInPath tDestination, pDestinationFolder
put _errorMsg("creating folder" && tDestination, the result) into tError
# Copy file or folder
if tError is empty then
put _resolveRelativeFilenameReference(tFilesA[i]["filename"], pRootFolder) into tFilename
if there is a file tFilename then
log " Copying file" && tFilename && "to" && tDestination
put URL("binfile:" & tFilename) into tFiledata
put _errorMsg("reading file contents [" & tFilename & "]", the result) into tError
# Send callbacks
if tError is empty then
if tFilesA[i]["callback stackfile"] is not empty then
log "Dispatching processCopyFilesFile to stack" && tFilesA[i]["callback stackfile"]
dispatch "processCopyFilesFile" to stack tFilesA[i]["callback stackfile"] \
with pBuildProfile, tFiledata, tDestination, the last item of tFilename, tFilesA[i]
# Store for unloading at end of packaging
put "" into sCallbackStacksA[ tFilesA[i]["callback stackfile"] ]
end if
put tFiledata into URL("binfile:" & tDestination & "/" & the last item of tFilename)
put _errorMsg("storing file" && tDestination & "/" & the last item of tFilename, the result) into tError
end if
else if there is a folder tFilename then
local tTargetFolder
put tDestination & "/" & the last item of tFilename into tTargetFolder
if there is a folder tTargetFolder then
_copyFilesAndFolders tFilename, tTargetFolder
put _errorMsg("copying folder" && tFilename && "to" && tTargetFolder, the result) into tError
else
_fileCopyFolder tFilename, tTargetFolder
put _errorMsg("copying folder" && tFilename && "to" && tTargetFolder, the result) into tError
end if
else
put "unable to locate file [" & tFilename & "]" into tError
end if
end if
if tError is not empty then exit repeat
end repeat
if tError is not empty then exit repeat
end repeat
end if
return tError for error
end _copyFiles
private command _packageHelpers pBuildProfile, @xAppA, pPlatform, pArchitecture, pOutputFolder, pExecutableOutputFolder
local tError, i, tIndex, tHelpersA
local tRootFolder
if the number of elements of xAppA["helpers"] > 0 then
log "Packaging Helpers for platform" && pPlatform
repeat with i = 1 to the number of elements of xAppA["helpers"]
if not _shouldAssetBeDistributed(xAppA["helpers"][i], pBuildProfile, pPlatform) then
log " Skipping helper:" && xAppA["helpers"][i]["filename"]
next repeat
end if
if there is not a folder xAppA["helpers"][i]["filename"] then
put "helper folder not found:" && xAppA["helpers"][i]["filename"] into tError
end if
if tError is empty then
_packageHelper pBuildProfile, xAppA["helpers"][i], pPlatform, pArchitecture, pOutputFolder, pExecutableOutputFolder
put the result into tError
end if
# Create new array with excluded elements removed
if tError is empty then
add 1 to tIndex
put xAppA["helpers"][i] into tHelpersA[tIndex]
end if
if tError is not empty then exit repeat
end repeat
# Store reindexed version of array
put tHelpersA into xAppA["helpers"]
end if
return tError for error
end _packageHelpers
private command _packageHelper pBuildProfile, @xHelperA, pPlatform, pArchitecture, pOutputFolder, pExecutableOutputFolder
local tError, tExclusions, tDestFolder, tExecutableDestFolder, tFilename, tFolderA, tFileA
local tKey, i
local tRootFolder
local tIndex, tHelpersA
set the itemdelimiter to "/"
put pOutputFolder & "/helpers/" & the last item of xHelperA["filename"] into tDestFolder
put pExecutableOutputFolder & "/helpers/" & the last item of xHelperA["filename"] into tExecutableDestFolder
# When creating all folders make sure we work from a common root folder
if pExecutableOutputFolder ends with ".app/Contents/MacOS" then
put item 1 to -3 of pOutputFolder into tRootFolder
else
put pOutputFolder into tRootFolder
end if
log " Adding helper:" && xHelperA["filename"]
packagerCreateAllFoldersInPath tDestFolder, tRootFolder
put the result into tError
if tError is empty then
# ui, libraries, frontscripts, backscripts, and behaviors
repeat for each item tKey in "ui/libraries/frontscripts/backscripts/behaviors"
put empty into tHelpersA
put 0 into tIndex
repeat with i = 1 to the number of elements of xHelperA[tKey]
if not _shouldAssetBeDistributed(xHelperA[tKey][i], pBuildProfile, pPlatform, pArchitecture) then
log " Skipping helper" && tKey && ":" && xHelperA[tKey]["filename"]
next repeat
end if
log " Adding" && tKey && "helper file:" && xHelperA[tKey][i]["filename"]
log " to folder:" && tDestFolder
_fileCopyFile xHelperA[tKey][i]["filename"], tDestFolder
put the result into tError
if tError is empty then
if xHelperA["encrypt"] is false OR xHelperA[tKey][i]["encrypt"] is false then
put tDestFolder & "/" & the last item of xHelperA[tKey][i]["filename"] & cr after tExclusions
end if
replace (xHelperA["filename"] & "/") with empty in xHelperA[tKey][i]["filename"]
# Create new array with excluded elements removed
add 1 to tIndex
put xHelperA[tKey][i] into tHelpersA[tIndex]
end if
if tError is not empty then exit repeat
end repeat
# Store reindexed version of array
if tError is empty then
put tHelpersA into xHelperA[tKey]
end if
if tError is not empty then exit repeat
end repeat
# externals
repeat for each key tKey in xHelperA["externals"]
if tKey is not pPlatform then
log " Skipping helper external platform" && tKey & ", target platform:" && pPlatform
delete local xHelperA["externals"][tKey]
else
put empty into tHelpersA
put 0 into tIndex
repeat with i = 1 to the number of elements of xHelperA["externals"][pPlatform]
if not _shouldAssetBeDistributed(xHelperA["externals"][pPlatform][i], pBuildProfile, pPlatform, pArchitecture) then
log " Skipping helper external:" && xHelperA["externals"][pPlatform][i]["filename"]
next repeat
end if
log " Adding external helper file:" && xHelperA["externals"][pPlatform][i]["filename"]
log " to folder:" && tExecutableDestFolder
packagerCreateAllFoldersInPath tExecutableDestFolder, tRootFolder
put the result into tError
if tError is empty then
if there is a file xHelperA["externals"][pPlatform][i]["filename"] then
_fileCopyFile xHelperA["externals"][pPlatform][i]["filename"], tExecutableDestFolder
else
_fileCopyFolder xHelperA["externals"][pPlatform][i]["filename"], \
tExecutableDestFolder & "/" & the last item of xHelperA["externals"][pPlatform][i]["filename"]
end if
put the result into tError
end if
if tError is empty then
replace (xHelperA["filename"] & "/") with empty in xHelperA["externals"][pPlatform][i]["filename"]
# Create new array with excluded elements removed
add 1 to tIndex
put xHelperA["externals"][pPlatform][i] into tHelpersA[tIndex]
end if
if tError is not empty then exit repeat
end repeat
# Store reindexed version of array
if tError is empty then
put tHelpersA into xHelperA["externals"][pPlatform]
end if
end if
if tError is not empty then exit repeat
end repeat
# extensions
if tError is empty then
put empty into tHelpersA
put 0 into tIndex
repeat with i = 1 to the number of elements of xHelperA["extensions"]
if not _shouldAssetBeDistributed(xHelperA["extensions"][i], pBuildProfile, pPlatform, pArchitecture) then
log " Skipping helper extension:" && xHelperA["extensions"][i]["filename"]
next repeat
end if
log " Adding extension helper file:" && xHelperA["extensions"][i]["filename"]
log " to folder:" && tExecutableDestFolder
packagerCreateAllFoldersInPath tExecutableDestFolder, tRootFolder
put the result into tError
# extension file
if tError is empty then
_fileCopyFile xHelperA["extensions"][i]["filename"], tExecutableDestFolder
put the result into tError
end if
# resource folder
if tError is empty then
if xHelperA["extensions"][i]["resource folder"] is not empty then
log " Adding extension helper resource folder:" && xHelperA["extensions"][i]["resource folder"]
log " to folder:" && tDestFolder
_fileCopyFolder xHelperA["extensions"][i]["resource folder"], tDestFolder
put the result into tError
if tError is empty then
replace (xHelperA["filename"] & "/") with empty in xHelperA["extensions"][i]["resource folder"]
end if
end if
end if
if tError is empty then
replace (xHelperA["filename"] & "/") with empty in xHelperA["extensions"][i]["filename"]
delete local xHelperA["extensions"][i]["source"]
# Create new array with excluded elements removed
add 1 to tIndex
put xHelperA["extensions"][i] into tHelpersA[tIndex]
end if
if tError is not empty then exit repeat
end repeat
# Store reindexed version of array
if tError is empty then
put tHelpersA into xHelperA["extensions"]
end if
end if
local tDestination
# folders
repeat for each element tFolderA in xHelperA["package folders"]
if not _shouldAssetBeDistributed(tFolderA, pBuildProfile, pPlatform, pArchitecture) then
log " Skipping helper folder:" && tFolderA["filename"]
next repeat
end if
put xHelperA["filename"] & "/" & tFolderA["filename"] into tFilename
if not (the last item of tFilename begins with ".") AND not (tFilename ends with ".bundle") AND there is not a folder (tDestFolder & "/" & the last item of tFilename) then
put tFolderA["destination"] into tDestination
if tDestination is empty then
put tDestFolder & "/" & the last item of tFilename into tDestination
else
put pOutputFolder & "/" & _normalizeRelativeFilename(tDestination) into tDestination
# tDestination may have been `./` or `/`.
if the last char of tDestination is "/" then
delete the last char of tDestination
end if
packagerCreateAllFoldersInPath tDestination, pOutputFolder
put _errorMsg("creating folder" && tDestination, the result) into tError
end if
log " Adding helper additional folder:" && tFilename
log " to folder:" && tDestination
_fileCopyFolder tFilename, tDestination
put the result into tError
end if
if tError is not empty then exit repeat
end repeat
# files
repeat for each element tFileA in xHelperA["package files"]
if not _shouldAssetBeDistributed(tFileA, pBuildProfile, pPlatform, pArchitecture) then
log " Skipping helper file:" && tFileA["filename"]
next repeat
end if
put xHelperA["filename"] & "/" & tFileA["filename"] into tFilename
put tFileA["destination"] into tDestination
if tDestination is empty then
put tDestFolder & "/" & the last item of tFilename into tDestination
else
put pOutputFolder & "/" & _normalizeRelativeFilename(tDestination) into tDestination
# tDestination may have been `./` or `/`.
if the last char of tDestination is "/" then
delete the last char of tDestination
end if
packagerCreateAllFoldersInPath tDestination, pOutputFolder
put _errorMsg("creating folder" && tDestination, the result) into tError
put "/" & the last item of tFilename after tDestination
end if
log " Adding helper additional file:" && tFilename
log " to file:" && tDestination
if tError is empty then
_fileCopyFile tFilename, tDestination
put the result into tError
end if
if tError is not empty then exit repeat
end repeat
if tError is empty then
_processStacksInFolder tDestFolder, sPassword, tExclusions
put the result into tError
end if
end if
put "helpers/" & the last item of xHelperA["filename"] into xHelperA["filename"]
return tError
end _packageHelper
private command _packageUI pBuildProfile, pPlatform, @xAppA, pOutputFolder
local tError, tLookupA
local tCompsA, tIndex, tKey, i
local tComponentFolder
set the itemdelimiter to "/"
repeat for each key tKey in xAppA["registered components"]["ui"]
put empty into tCompsA
put 0 into tIndex
put tKey into tLookupA
split tLookupA by kNestedKeysDelimiter
if the number of elements of xAppA[tLookupA] > 0 then
log "Packaging" && tKey
create folder (pOutputFolder & "/" & tKey)
put _errorMsg("creating folder" && pOutputFolder & "/" & tKey, the result) into tError
if tError is empty then
local tDestFolder, tExclusions
put empty into tExclusions
# Build list of stacks that are excluded from password protection
repeat with i = 1 to the number of elements of xAppA[tLookupA]
if xAppA[tLookupA][i]["encrypt"] is false then
put item 1 to -2 of xAppA[tLookupA][i]["filename"] into tComponentFolder
put pOutputFolder & "/" & tKey & "/" & the last item of tComponentFolder into tDestFolder
put tDestFolder & "/" & the last item of xAppA[tLookupA][i]["filename"] & cr after tExclusions
end if
end repeat
delete the last char of tExclusions
log " UI stacks excluded from encryption for key" && tKey & ":" && tExclusions
repeat with i = 1 to the number of elements of xAppA[tLookupA]
if not _shouldAssetBeDistributed(xAppA[tLookupA][i], pBuildProfile, pPlatform) then
log " Skipping ui asset" && tKey && ":" && xAppA[tLookupA][i]["filename"]
next repeat
end if
put item 1 to -2 of xAppA[tLookupA][i]["filename"] into tComponentFolder
put pOutputFolder & "/" & tKey & "/" & the last item of tComponentFolder into tDestFolder
log " Adding component:" && xAppA[tLookupA][i]["filename"]
# If two entries in array are in same folder only copy and process the folder once.
if there is not a folder tDestFolder then
_fileCopyFolder tComponentFolder, tDestFolder
put _errorMsg("copying folder" && tComponentFolder && "to" && tDestFolder, the result) into tError
if tError is empty then
_processStacksInFolder tDestFolder, sPassword, tExclusions
put the result into tError
end if
# Send callbacks
if tError is empty then
if xAppA["registered components"]["ui"][tKey]["callback stackfile"] is not empty then
log "Dispatching finalizePackagedUIAsset to stack" && xAppA["registered components"]["ui"][tKey]["callback stackfile"]
dispatch "finalizePackagedUIAsset" to stack xAppA["registered components"]["ui"][tKey]["callback stackfile"] \
with pBuildProfile, pPlatform, tDestFolder
# Store for unloading at end of packaging
put "" into sCallbackStacksA[ xAppA["registered components"]["ui"][tKey]["callback stackfile"] ]
end if
end if
end if
add 1 to tIndex
put tKey & "/" & the last item of tComponentFolder & "/" & the last item of xAppA[tLookupA][i]["filename"] into tCompsA[tIndex]["filename"]
put xAppA[tLookupA][i]["name"] into tCompsA[tIndex]["name"]
put xAppA[tLookupA][i]["autoload"] into tCompsA[tIndex]["autoload"]
if tError is not empty then exit repeat
end repeat
end if
if tError is empty then
put tCompsA into xAppA[tLookupA]
end if
end if
end repeat
return tError
end _packageUI
private command _packageFiles pBuildProfile, pPlatform, @xAppA, pOutputFolder
local tError, tLookupA
local tCompsA, tKey, tProp, i
local tComponentFolder
set the itemdelimiter to "/"
repeat for each key tKey in xAppA["registered components"]["files"]
put empty into tCompsA
put tKey into tLookupA
split tLookupA by kNestedKeysDelimiter
if the number of elements of xAppA[tLookupA] > 0 then
log "Packaging" && tKey
local tFilename
local tIndex
put 0 into tIndex
repeat with i = 1 to the number of elements of xAppA[tLookupA]
# Send callbacks
if xAppA["registered components"]["files"][tKey]["callback stackfile"] is not empty then
log "Dispatching processPackagedFileAsset to stack" && xAppA["registered components"]["files"][tKey]["callback stackfile"]
dispatch "processPackagedFileAsset" to stack xAppA["registered components"]["files"][tKey]["callback stackfile"] \
with pBuildProfile, pPlatform, xAppA[tLookupA][i], pOutputFolder
# Store for unloading at end of packaging
put "" into sCallbackStacksA[ xAppA["registered components"]["files"][tKey]["callback stackfile"] ]
end if
# Skip if no being distributed
if not _shouldAssetBeDistributed(xAppA["registered components"]["files"][tKey], pBuildProfile, pPlatform) \
or not _shouldAssetBeDistributed(xAppA[tLookupA][i], pBuildProfile, pPlatform) then
log " Skipping file asset" && tKey && ":" && xAppA[tLookupA][i]["filename"]
next repeat
end if
put xAppA[tLookupA][i]["filename"] into tFilename
replace levureAppFolder() with pOutputFolder in tFilename
packagerCreateAllFoldersInPath item 1 to -2 of tFilename, pOutputFolder
put _errorMsg("creating folder" && item 1 to -2 of tFilename, the result) into tError
if tError is empty then
_fileCopyFile xAppA[tLookupA][i]["filename"], tFilename
put _errorMsg("copying file" && xAppA[tLookupA][i]["filename"] && "to" && \
tFilename, the result) into tError
end if
if tError is empty then
# Create new array with excluded elements removed
add 1 to tIndex
put _makeRelativePath(xAppA[tLookupA][i]["filename"],levureAppFolder()) into tCompsA[tIndex]["filename"]
repeat for each key tProp in xAppA[tLookupA][i]
if tProp is "filename" then next repeat
put xAppA[tLookupA][i][tProp] into tCompsA[tIndex][tProp]
end repeat
end if
if tError is not empty then exit repeat
end repeat
if tError is empty then
put tCompsA into xAppA[tLookupA]
end if
end if
end repeat
return tError
end _packageFiles
private function _shouldAssetBeDistributed pAssetA, pBuildProfile, pPlatform, pArchitecture
set the wholematches to true
if pAssetA["distribute"] is false then
return false
else if pAssetA["engine version"] is not empty and not __useLevureFileWithThisEngine(pAssetA["engine version"]) then
return false
else if pAssetA["platform"] is not empty and pPlatform is not among the items of pAssetA["platform"] then
return false
else if pAssetA["architecture"] is not empty and pAssetA["architecture"] is not pArchitecture then
return false
else
return true
end if
end _shouldAssetBeDistributed
private function _shouldBuildForPlatform pPlatform, pFilter
return _doesStringPassFilter(pPlatform, pFilter)
end _shouldBuildForPlatform
/**
Summary: Returns true if pString passes pFilter
*/
private function _doesStringPassFilter pString, pFilter
if pFilter is empty then return true
local tFilter
local tPerformedAPositiveMatchCheck = "false"
repeat for each item tFilter in pFilter
if char 1 of tFilter is "!" then
# Match means false
if pString is char 2 to -1 of tFilter then
return false
end if
else
# Match means true
if pString is tFilter then
return true
else
put true into tPerformedAPositiveMatchCheck
end if
end if
end repeat
# If nothing matched but a check for a positive match was performed then
# return false. Otherwise return true.
if tPerformedAPositiveMatchCheck then
return false
else
return true
end if
end _doesStringPassFilter
private command _processStacksInFolder pFolder, pPassword, pExclusions
local tError
local tFolders, tFolder, tFiles, tFile
log "Processing stacks in folder:" && pFolder
set the wholematches to true
lock messages
put _fileListing(pFolder) into tFiles
repeat for each line tFile in tFiles
if there is a stack tFile then
if the scriptonly of stack tFile then
set the scriptonly of stack tFile to false
if pPassword is not empty AND tFile is not among the lines of pExclusions then
_setPassword tFile, pPassword
end if
save stack tFile as tFile with newest format
put the result into tError
else
if pPassword is not empty AND tFile is not among the lines of pExclusions \
AND the password of stack tFile is empty then
_setPassword tFile, pPassword
save stack tFile as tFile with newest format
put the result into tError
end if
end if
# When loading a stack into memory the stackfiles may be loaded into memory
_unloadStackAndStackFiles tFile
end if
if tError is not empty then exit repeat
end repeat
if tError is empty then
put _folderListing(pFolder) into tFolders
repeat for each line tFolder in tFolders
_processStacksInFolder tFolder, pPassword
put the result into tError
if tError is not empty then exit repeat
end repeat
end if
unlock messages
return tError
end _processStacksInFolder
private command _unloadStackAndStackFiles pStack
local tStackEntry, tStack
repeat for each line tStackEntry in the stackfiles of stack pStack
put item 1 of tStackEntry into tStack
if tStack is not empty AND there is a stack tStack then
delete stack tStack
end if
end repeat
delete stack pStack
return empty
end _unloadStackAndStackFiles
private command _packageExtensions pBuildProfile, @xAppA, pPlatform, pOutputFolder, pExtensionsBuiltIntoStandalone
local tError
local tExtsA, tIndex, i
set the itemdelimiter to "/"
set the wholematches to true
if the number of elements of xAppA["extensions"] > 0 then
log "Packaging extensions"
log "Extensions build into standalone:" && pExtensionsBuiltIntoStandalone
repeat with i = 1 to the number of elements of xAppA["extensions"]
# We don't need to add extensions that are being built with the standalone
if _getKindFromExtensionFile(xAppA["extensions"][i]["filename"]) is not among the lines of pExtensionsBuiltIntoStandalone \
and _shouldAssetBeDistributed(xAppA["extensions"][i], pBuildProfile, pPlatform) then
if there is not a folder (pOutputFolder & "/extensions") then
create folder (pOutputFolder & "/extensions")
put _errorMsg("creating the extensions folder [" & pOutputFolder & "/extensions" & "]", the result) into tError
if tError is not empty then
exit repeat
end if
end if
log " Adding extension:" && xAppA["extensions"][i]["filename"]
_fileCopyFile xAppA["extensions"][i]["filename"], pOutputFolder & "/extensions/" & the last item of xAppA["extensions"][i]["filename"]
put _errorMsg("copying file [" & xAppA["extensions"][i]["filename"] & "] to [" & pOutputFolder & "/extensions/" & the last item of xAppA["extensions"][i]["filename"] & "]", the result) into tError
if tError is empty then
add 1 to tIndex
put "extensions/" & the last item of xAppA["extensions"][i]["filename"] into tExtsA[tIndex]["filename"]
if xAppA["extensions"][i]["resource folder"] is not empty then
# TODO: need to copy the resource folder and make sure it has a unique name
end if
end if
end if
if tError is not empty then exit repeat
end repeat
if tError is empty then
put tExtsA into xAppA["extensions"]
end if
end if
return tError for error
end _packageExtensions
private function _getKindFromExtensionFile pFilename
local tError, tKind, it, i
open file pFilename for binary read
put the result into tError
if tError is empty then
seek to 6 in file pFilename
put the result into tError
if tError is empty then
repeat forever
add 1 to i
read from file pFilename for 1 byte
put the result into tError
if tError is empty then
if byteToNum(it) < 46 then # Identifier: [A-Za-z][A-Za-z0-9.]* (46 is ".")
exit repeat
else
put it after tKind
end if
end if
if tError is not empty then exit repeat
end repeat
close file pFilename
end if
end if
if tError is empty then
return tKind for value
else
return tError for error
end if
end _getKindFromExtensionFile
private command _packageAssetStacks pBuildProfile, @xAppA, pPlatform, pOutputFolder
local tError, i
local tStacksToLoad
local tStackName, tFilename
lock messages
# behaviors
if tError is empty then
if the number of elements of xAppA["behaviors"] > 0 then
log "Packaging behaviors"
put "Levure Application Behaviors" into tStackName
create invisible stack tStackName
if sPassword is not empty then
_setPassword tStackName, sPassword
end if
repeat with i = 1 to the number of elements of xAppA["behaviors"]
if not _shouldAssetBeDistributed(xAppA["behaviors"][i], pBuildProfile, pPlatform) then next repeat
if xAppA["behaviors"][i]["encrypt"] is not false and sPassword is not empty then
# Just in case there are duplicate entries in array
if the password of stack xAppA["behaviors"][i]["filename"] is empty then
set the scriptOnly of stack xAppA["behaviors"][i]["filename"] to false
_setPassword xAppA["behaviors"][i]["filename"], sPassword
end if
end if
log " Adding behavior stack:" && xAppA["behaviors"][i]["filename"]
set the mainstack of stack xAppA["behaviors"][i]["filename"] to tStackName
end repeat
log "Saving behavior stack:" && pOutputFolder & "/behaviors.livecode"
save stack tStackName as (pOutputFolder & "/behaviors.livecode") with newest format
put the result into tError
delete stack tStackName
if tError is empty then
put empty into xAppA["behaviors"]
put "behaviors.livecode" into xAppA["behaviors"][1]["filename"]
end if
end if
end if
# libraries, frontscripts, backscript
if tError is empty then
local tProp, tStandaloneLibraryStacksA, tFile, tstlibIndex
# union tStandaloneLibraryStacksA with xAppA[tAssetType] at the end. xAppA[tAssetType] will have a [1] key.
put 1 into tstlibIndex
repeat for each item tAssetType in "frontscripts,backscripts,libraries"
put empty into tStacksToLoad
if the number of elements of xAppA[tAssetType] is 0 then next repeat
put "Levure Application" && toupper(char 1 of tAssetType) & char 2 to -1 of tAssetType into tStackName
create invisible stack tStackName
if sPassword is not empty then
_setPassword tStackName, sPassword
end if
repeat with i = 1 to the number of elements of xAppA[tAssetType]
if not _shouldAssetBeDistributed(xAppA[tAssetType][i], pBuildProfile, pPlatform) then next repeat
if xAppA[tAssetType][i]["encrypt"] is not false and sPassword is not empty then
# Just in case there are duplicate entries in array
if the password of stack xAppA[tAssetType][i]["filename"] is empty then
set the scriptOnly of stack xAppA[tAssetType][i]["filename"] to false
_setPassword xAppA[tAssetType][i]["filename"], sPassword
end if
end if
log " Adding" && tAssetType && "stack:" && xAppA[tAssetType][i]["filename"]
if tAssetType is "libraries" and the substacks of stack xAppA[tAssetType][i]["filename"] is not empty then
put _fileExtractName(xAppA[tAssetType][i]["filename"]) into tFile
save stack xAppA[tAssetType][i]["filename"] as (pOutputFolder & "/" & tFile) with newest format
add 1 to tstlibIndex
put tFile into tStandaloneLibraryStacksA[tstlibIndex]["filename"]
repeat for each key tProp in xAppA[tAssetType][i]
if tProp is "filename" then
put the short name of stack xAppA[tAssetType][i]["filename"] into tStandaloneLibraryStacksA[tstlibIndex]["name"]
else
put xAppA[tAssetType][i][tProp] into tStandaloneLibraryStacksA[tstlibIndex][tProp]
end if
end repeat
else
set the mainstack of stack xAppA[tAssetType][i]["filename"] to tStackName
repeat for each key tProp in xAppA[tAssetType][i]
if tProp is "filename" then
put the short name of stack xAppA[tAssetType][i]["filename"] into tStacksToLoad[i]["name"]
else
put xAppA[tAssetType][i][tProp] into tStacksToLoad[i][tProp]
end if
end repeat
end if
end repeat
set the uStacksToLoad of stack tStackName to tStacksToLoad
log "Saving" && tAssetType && "stack:" && pOutputFolder & "/" & tAssetType & ".livecode"
save stack tStackName as (pOutputFolder & "/" & tAssetType & ".livecode") with newest format
put _errorMsg("saving" && tAssetType && "stack [" & pOutputFolder & "/" & tAssetType & ".livecode" & "]", the result) into tError
delete stack tStackName
if tError is empty then
put empty into xAppA[tAssetType]
put tAssetType & ".livecode" into xAppA[tAssetType][1]["filename"]
log "standalone libraries:" && _printArray(tStandaloneLibraryStacksA)
union xAppA[tAssetType] with tStandaloneLibraryStacksA
end if
end repeat
end if
unlock messages
return tError
end _packageAssetStacks
private command _copyExecutablesAndSupportingFilesToFolder pBuildForPlatform, pBuildForDistribution, pBuildFolder, pOutputFolder
local tError
local tFolderContainingBuild
local tFiles, tFile
put _folderListing(pBuildFolder) into tFolderContainingBuild
set the itemdelimiter to "/"
if pBuildForDistribution then
local tNewFolderName
put item 1 to -2 of tFolderContainingBuild & "/" & pBuildForPlatform into tNewFolderName
# rename first in case folder is being moved to a network volume on Windows.
rename folder tFolderContainingBuild to tNewFolderName
put _errorMsg("renaming folder [" & tFolderContainingBuild & "] to [" & tNewFolderName & "]", the result) into tError
if tError is empty then
_moveFolder tNewFolderName, pOutputFolder
put _errorMsg("moving folder [" & tNewFolderName & "] to [" & pOutputFolder & "]", the result) into tError
end if
else
_moveFilesAndFolders tFolderContainingBuild, pOutputFolder
put the result into tError
end if
return tError for error
end _copyExecutablesAndSupportingFilesToFolder
private function _generateStartupScript pRelativeFrameworkPath, pRelativeAppPath, pBuildForDistribution
local tScript, tFunction
if pBuildForDistribution then
# load app stack into memory on startup
local tFrameworkFilename, tAppStackFilename
set the itemdelimiter to "/"
put pRelativeAppPath into tFrameworkFilename # Framework and app stack will be alongside each other when packaged
put pRelativeAppPath into tAppStackFilename
put the last item of levureFrameworkFilename() into item (the number of items of tFrameworkFilename + 1) of tFrameworkFilename
put the last item of levureAppStackFilename() into item (the number of items of tAppStackFilename + 1) of tAppStackFilename
put "on startup" & cr & \
_qstr("set the itemDelimiter to `/`") & cr & \
_qstr("if the platform is `macos` then") & cr & \
_qstr("put resolvePath(`" & tAppStackFilename & "`, item 1 to -2 of the engine folder & `/Resources/_MacOS`) into tAppStackFilename") & cr & \
_qstr("put resolvePath(`" & tFrameworkFilename & "`, item 1 to -2 of the engine folder & `/Resources/_MacOS`) into tFrameworkFilename") & cr & \
_qstr("else if the platform is `web` then") & cr & \
_qstr("put resolvePath(`" & tAppStackFilename & "`, the engine folder & `/standalone`) into tAppStackFilename") & cr & \
_qstr("put resolvePath(`" & tFrameworkFilename & "`, the engine folder & `/standalone`) into tFrameworkFilename") & cr & \
"else" & cr & \
_qstr("put resolvePath(`" & tAppStackFilename & "`, the engine folder) into tAppStackFilename") & cr & \
_qstr("put resolvePath(`" & tFrameworkFilename & "`, the engine folder) into tFrameworkFilename") & cr & \
"end if" & cr & \
"put there is a stack tFrameworkFilename into tLoaded" & cr & \
"if tLoaded then" & cr & \
_qstr("set the behavior of me to the long id of stack tFrameworkFilename") & cr & \
"else" & cr & \
_qstr("answer `Could not locate app stack: ` & tFrameworkFilename") & cr & \
"quit" & cr & \
"end if" & cr & \
"put there is a stack tAppStackFilename into tLoaded" & cr & \
"if not tLoaded then" & cr & \
_qstr("answer `Could not locate app stack: ` & tAppStackFilename") & cr & \
"quit" & cr & \
"end if" & cr & \
"end startup" into tScript
else
# Add code to locate framework and app files
put "appFolder()" into tFunction
put "on startup" & cr & \
_qstr("put resolvePath(`" & pRelativeFrameworkPath & "`, " & tFunction & ") into tStackfile") & cr & \
"set the behavior of me to the long id of stack tStackfile" & cr & \
"end startup" into tScript
put cr & cr & \
"function levureTestingStandaloneAppFolder" & cr & \
_qstr("put resolvePath(`" & pRelativeAppPath & "`, " & tFunction & ") into tStackfile") & cr & \
"return tStackfile for value" & cr & \
"end levureTestingStandaloneAppFolder" after tScript
put cr & cr & \
"function levureTestingRevCustomizationFolder" & cr & \
_qstr("return `" & revEnvironmentCustomizationPath() & "` for value") & cr & \
"end levureTestingRevCustomizationFolder" after tScript
put cr & cr & \
"function levureTestingStandaloneFrameworkPath" & cr & \
_qstr("return resolvePath(`" & pRelativeFrameworkPath & "`, " & tFunction & ")") & cr & \
"end levureTestingStandaloneFrameworkPath" after tScript
put cr & cr & \
"private function appFolder" & cr & \
"set the itemdelimiter to slash" & cr & \
"put the effective filename of me into tFilename" & cr & \
"delete the last item of tFilename" & cr & \
_qstr("if the platform is `macos` and tFilename contains `.app/Contents/MacOS` then") & cr & \
"delete item -3 to -1 of tFilename" & cr & \
"end if" & cr & \
"return tFilename" & cr & \
"end appFolder" after tScript
end if
put cr & cr & \
"private function resolvePath pFilename, pRootFolder" & cr & \
_qstr("set the itemDelimiter to `/`") & cr & \
_qstr("repeat while pFilename begins with `../`") & cr & \
"if pRootFolder is empty then" & cr & \
_qstr("return `relative path is too deep for root folder` for error") & cr & \
"else" & cr & \
"delete the last item of pRootFolder" & cr & \
"delete char 1 to 3 of pFilename" & cr & \
"end if" & cr & \
"end repeat" & cr & \
_qstr("return pRootFolder & `/` & pFilename for value") & cr & \
"end resolvePath" after tScript
return tScript for value
end _generateStartupScript
private command _validateBuildProfile pBuildProfile
local tError
put word 1 to -1 of line 1 of pBuildProfile into pBuildProfile
if pBuildProfile is empty then put "invalid build profile" into tError
if tError is empty then
if pBuildProfile is not "test" and pBuildProfile is not among the keys of sAppA["build profiles"] then
put "invalid build profile" into tError
end if
end if
return tError for error
end _validateBuildProfile
private command _performPackagingPrechecks pStandaloneStackFilename, @xBuildProfile
local tError, tLoaded
put empty into sPassword
put tolower(xBuildProfile) into xBuildProfile
_validateBuildProfile xBuildProfile
put the result into tError
if tError is empty then
put there is a stack pStandaloneStackFilename into tLoaded
if not tLoaded then
put "invalid standalone stack" into tError
end if
end if
# Get password for encryption
if tError is empty and xBuildProfile is not "test" then
if levureAppGet("encrypt stacks") is "random" then
put uuid() into sPassword
else if levureAppGet("encrypt stacks") is "password" or levureAppGet("encrypt stacks") is true then
put levureAppGet("password") into sPassword
if sPassword is empty then
local tEnvA
put levureAppGetENV() into tEnvA
put tEnvA["PASSWORD"] into sPassword
end if
if sPassword is empty then
put "no password was provided to encrypt stacks" into tError
end if
end if
end if
if tError is empty then
if not (levureAppFolder() begins with levureStandaloneFolder()) then
put "the app.yml file must be in the same directory or a subdirectory of the standalone stack" into tError
end if
end if
return tError for error
end _performPackagingPrechecks
private command _moveFilesAndFolders pSourceFolder, pDestinationFolder
local tError, tFile, tNewFilename
set the itemdelimiter to "/"
if tError is empty then
repeat for each line tFile in _fileListing(pSourceFolder)
put pDestinationFolder & "/" & the last item of tFile into tNewFilename
rename file tFile to tNewFilename
put _errorMsg("renaming file [" & tFile & "] to [" & tNewFilename & "]", the result) into tError
if tError is not empty then exit repeat
end repeat
end if
if tError is empty then
repeat for each line tFile in _folderListing(pSourceFolder)
put pDestinationFolder & "/" & the last item of tFile into tNewFilename
rename folder tFile to tNewFilename
put _errorMsg("renaming folder [" & tFile & "] to [" & tNewFilename & "]", the result) into tError
if tError is not empty then exit repeat
end repeat
end if
return tError
end _moveFilesAndFolders
private command _copyFilesAndFolders pSourceFolder, pDestinationFolder
local tError, tFile
set the itemdelimiter to "/"
if tError is empty then
repeat for each line tFile in _fileListing(pSourceFolder)
_fileCopyFile tFile, pDestinationFolder & "/" & the last item of tFile
put the result into tError
if tError is not empty then exit repeat
end repeat
end if
if tError is empty then
repeat for each line tFile in _folderListing(pSourceFolder)
_fileCopyFolder tFile, pDestinationFolder & "/" & the last item of tFile
put the result into tError
if tError is not empty then exit repeat
end repeat
end if
return tError
end _copyFilesAndFolders
private command _updateStandaloneVersionInfo pStackFilename
local tError
local tVersion, tMajor, tMinor, tRevision, tBuild
put levureAppGet("version") into tVersion
put levureAppGet("build") into tBuild
set the itemdelimiter to "."
put item 1 of tVersion into tMajor
put item 2 of tVersion into tMinor
put item 3 of tVersion into tRevision
## -- Windows
set the cRevStandaloneSettings["Windows,fileversion1"] of stack pStackFilename to tMajor
set the cRevStandaloneSettings["Windows,fileversion2"] of stack pStackFilename to tMinor
set the cRevStandaloneSettings["Windows,fileversion3"] of stack pStackFilename to tRevision
set the cRevStandaloneSettings["Windows,fileversion4"] of stack pStackFilename to tBuild
set the cRevStandaloneSettings["Windows,productversion1"] of stack pStackFilename to tMajor
set the cRevStandaloneSettings["Windows,productversion2"] of stack pStackFilename to tMinor
set the cRevStandaloneSettings["Windows,productversion3"] of stack pStackFilename to tRevision
set the cRevStandaloneSettings["Windows,productversion4"] of stack pStackFilename to tBuild
## -- macOS
if the cRevStandaloneSettings["OSX,plist"] of stack pStackFilename is not empty then
local tPlist
put the filename of stack pStackFilename into tPlist
set the itemdelimiter to slash
put the cRevStandaloneSettings["OSX,plist"] of stack pStackFilename into the last item of tPlist
if there is a file tPlist then
local tData
## Developer is doing this the proper way. Update plist
put URL("binfile:" & tPlist) into tData
put _errorMsg("opening plist file [" & tPlist & "]", the result) into tError
if tError is empty then
_updatePlistInfo tData
put tData into URL("binfile:" & tPlist)
put the result into tError
if tError is not empty then
put "error writing plist info: " & tError into tError
end if
end if
else
put "could not find plist file" && quote & tPlist & quote into tError
end if
else
## update standalone settings
set the cRevStandaloneSettings["OSX,shortversion"] of stack pStackFilename \
to format("%u.%u.%u", tMajor, tMinor, tRevision)
set the cRevStandaloneSettings["OSX,longVersion"] of stack pStackFilename \
to format("%u.%u.%u (%u)", tMajor, tMinor, tRevision, tBuild)
end if
## -- iOS
set the cRevStandaloneSettings["ios,bundle version"] of stack pStackFilename to format("%u.%u.%u", tMajor, tMinor, tRevision)
set the cRevStandaloneSettings["ios,bundle build"] of stack pStackFilename to tBuild
## -- Android
set the cRevStandaloneSettings["android,version name"] of stack pStackFilename to format("%u.%u.%u", tMajor, tMinor, tRevision)
set the cRevStandaloneSettings["android,version code"] of stack pStackFilename to tBuild
return tError for error
end _updateStandaloneVersionInfo
private command _updatePlistInfo @xData, pVersionA
local tRangeA, tVersion, tBuild, tString
put levureAppGet("version") into tVersion
put levureAppGet("build") into tBuild
put _findRangeForPlistEntry(xData, "CFBundleGetInfoString") into tRangeA
if tRangeA["start"] > 0 then
put char tRangeA["start"] to tRangeA["end"] of xData into tString
put tVersion && "build" && tBuild into item 1 of tString
put tString into char tRangeA["start"] to tRangeA["end"] of xData
end if
put _findRangeForPlistEntry(xData, "CFBundleShortVersionString") into tRangeA
if tRangeA["start"] > 0 then
put tVersion into char tRangeA["start"] to tRangeA["end"] of xData
end if
put _findRangeForPlistEntry(xData, "CFBundleVersion") into tRangeA
if tRangeA["start"] > 0 then
put tBuild into char tRangeA["start"] to tRangeA["end"] of xData
end if
put _findRangeForPlistEntry(xData, "CFBundleLongVersionString") into tRangeA
if tRangeA["start"] > 0 then
put tVersion & "." & tBuild into char tRangeA["start"] to tRangeA["end"] of xData
end if
end _updatePlistInfo
private function _findRangeForPlistEntry pData, pEntry
local tRangeA, tFirstCharNo, tStartCharNo, tEndCharNo
put 0 into tRangeA["start"]
put 0 into tRangeA["end"]
put offset("" & pEntry & "", pData) into tFirstCharNo
if tFirstCharNo > 0 then
put offset("", pData, tFirstCharNo) into tStartCharNo
if tStartCharNo > 0 then
put offset("", pData, tFirstCharNo) into tEndCharNo
if tEndCharNo > 0 then
add tFirstCharNo to tStartCharNo
add tFirstCharNo to tEndCharNo
put tStartCharNo + 8 into tRangeA["start"]
put tEndCharNo - 1 into tRangeA["end"]
end if
end if
end if
return tRangeA
end _findRangeForPlistEntry
private command _createMacOsPkg pBuildProfile, pAppA, pOutputFolder, pFolderWithMacApp
local tAppBundle, tInstallerName
local tPackage, tCmd, tResult
set the itemdelimiter to "/"
put pFolderWithMacApp & "/" & the last item of levureStandaloneFilename() into tAppBundle
if there is not a folder tAppBundle then return empty
put packagerGetInstallerName(pBuildProfile, "macos") into tInstallerName
if tInstallerName is empty then return empty
put pOutputFolder & "/" & tInstallerName & ".pkg" into tPackage
put format("productbuild --sign \"%s\" --component \"%s\" /Applications \"%s\"", pAppA["macos installer signing certificate"], tAppBundle, tPackage) into tCmd
packagerLog "productbuild shell command:" && tCmd
put shell(tCmd) into tResult
if the result > 0 then
return tResult for error
else
return empty for value
end if
end _createMacOsPkg
function packagerGetInstallerName pBuildProfile, pPlatform
local tName, tProfilesA
put levureAppGet("build profiles") into tProfilesA
put tProfilesA[pBuildProfile]["installer name"][pPlatform] into tName
if tName is empty then
put tProfilesA["all profiles"]["installer name"][pPlatform] into tName
end if
if tName is empty then
put tProfilesA[pBuildProfile]["installer name"]["all platforms"] into tName
end if
if tName is empty then
put tProfilesA["all profiles"]["installer name"]["all platforms"] into tName
end if
if tName is not empty then
put tName && levureAppGet("version") & "-" & levureAppGet("build") into tName
end if
return tName
end packagerGetInstallerName
private command _signMacOSApplication pBuildProfile, pAppA, pFolderWithMacApp
local tError, tAppBundle
set the itemdelimiter to "/"
put pFolderWithMacApp & "/" & the last item of levureStandaloneFilename() into tAppBundle
if there is a folder tAppBundle then
if pAppA["macos signing certificate"] is not empty then
log "Signing application for macOS using cert:" && pAppA["macos signing certificate"]
local tCmd
# Sierra on up needs extended attributes stripped out
if tError is empty then
put format("chmod -R u+rw \"%s\"", tAppBundle) into tCmd
get shell(tCmd)
if the result > 0 then
put it into tError
end if
end if
if tError is empty then
put format("xattr -rc \"%s\"", tAppBundle) into tCmd
get shell(tCmd)
if the result > 0 then
put it into tError
end if
end if
if tError is empty then
_signAndStrip tAppBundle, pAppA["macos signing certificate"]
put the result into tError
end if
if tError is empty then
put format("codesign -dvvv \"%s\"", tAppBundle) into tCmd
get shell(tCmd)
if the result > 0 then
put it into tError
end if
end if
if tError is empty then
if pAppA["macos signing certificate"] begins with "3rd Party Mac Developer Application" then
put format("spctl --assess --verbose --ignore-cache --type execute -v \"%s\"", tAppBundle) into tCmd
get shell(tCmd)
if the result > 0 then
# Don't stop because of this
put "spctl error:" && it into tError
end if
end if
end if
end if
end if
return tError for error
end _signMacOSApplication
## Monte Goulding with modifications by Trevor DeVore
command _signAndStrip pBundle, pCertificate
local tError
local tFolders, tFolder, tRootFolder
/*
List of folders from Apple docs:
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html#//apple_ref/doc/uid/TP40005929-CH4-TNTAG201
Exception is `/Resources` which is folder where .frameworks can store files.
For example, Sparkle stores Autoupdate.app in the `/Resources` folder.
*/
put "/Contents/Frameworks,/Contents/MacOS,/Contents/Frameworks,/Contents/PlugIns," & \
"/Contents/XPCServices,/Contents/Helpers,/Contents/Library/Automator," & \
"/Contents/Library/Spotlight,/Contents/Library/LoginItems,/Contents/Library/LaunchServices," & \
"/Resources" into tFolders
# recursively parse standard locations for code inside of a bundle
if tError is empty then
repeat for each item tFolder in tFolders
put pBundle & tFolder into tRootFolder
if there is a folder tRootFolder then
_findBundlesAndStripExecutables tRootFolder, pCertificate
put the result into tError
end if
if tError is not empty then exit repeat
end repeat
end if
if tError is empty then
_signFile pBundle, pCertificate
put the result into tError
end if
return tError
end _signAndStrip
# Signs a file or folder
private command _signFile pFilename, pCertificate
local tEntitlementsFile, tCmd, tError, tResult
if tError is empty then
put _entitlementFile(pFilename) into tEntitlementsFile
if tEntitlementsFile is not empty then
put format("codesign --verbose --force --options=runtime --timestamp -s \"%s\" --entitlements \"%s\" \"%s\"", pCertificate, tEntitlementsFile, pFilename) into tCmd
else
put format("codesign --verbose --force --options=runtime --timestamp -s \"%s\" \"%s\"", pCertificate, pFilename) into tCmd
end if
log "codesign cmd:" && tCmd
get shell(tCmd)
if the result > 0 then
put the result into tError
if tError is 1 then
put "codesign process failed:" && it into tError
else if tError is 2 then
put "invalid arguments passed to codesign:" && it into tError
else if tError is 3 then
put "requirements not met for -R flag in codesign:" && it into tError
else
put it into tError
end if
end if
end if
return tError for error
end _signFile
/**
Summary: Looks for an entitlement file to use with a file.
Parameters:
pFilename: Path to an .app, .framework, or executable file.
Returns: Full path to .entitlements file
*/
function _entitlementFile pFilename
local tFiles, tFileShortName
# Look for an entitlement file that matches the bundle name
set the itemdelimiter to "/"
put the last item of pFilename into tFileShortName
set the itemDelimiter to "."
if the number of items of tFileShortName > 1 then
delete item -1 of tFileShortName
end if
if there is a folder (pFilename & "/Contents/Resources") then
put _fileListing(pFilename & "/Contents/Resources", true) into tFiles
else if there is a folder (pFilename & "/Contents") then
put _fileListing(pFilename & "/Contents", true) into tFiles
else if pFilename contains ".app/Contents/MacOS" then
# Look for entitlement for executable that was moved from ./Resources/_MacOS
replace ".app/Contents/MacOS" with ".app/Contents/Resources/_MacOS" in pFilename
set the itemdelimiter to "/"
put _fileListing(item 1 to -2 of pFilename, true) into tFiles
end if
filter tFiles with "*/" & tFileShortName & ".entitlements"
return tFiles
end _entitlementFile
private function _isMacOSExecutable pFilepath
return pFilepath ends with ".bundle" \
OR pFilepath ends with ".dylib" \
OR pFilepath ends with ".framework" \
OR pFilepath ends with ".app"
end _isMacOSExecutable
## Monte Goulding with modifications by Trevor DeVore
private command _findBundlesAndStripExecutables pFolder, pCertificate
local tError, tOldDefaultFolder, tFolders, tFilesInfo, tFiles, tResult
# Get list of folders and reset. Can't wait until end as it messes up recursive calls.
put the defaultFolder into tOldDefaultFolder
set the defaultFolder to pFolder
put the folders into tFolders
put the detailed files into tFilesInfo
put the files into tFiles
set the defaultFolder to tOldDefaultFolder
repeat for each line tFolder in line 2 to -1 of tFolders
if _isMacOSExecutable(tFolder) then
_signAndStrip pFolder & "/" & tFolder, pCertificate
put the result into tError
else
_findBundlesAndStripExecutables pFolder & "/" & tFolder, pCertificate
put the result into tError
end if
if tError is not empty then exit repeat
end repeat
# lipo and sign executables
if tError is empty then
local tFilename, tCmd
repeat for each line tFile in tFilesInfo
put URLDecode(item 1 of tFile) into tFilename
# If executable bit is set then lipo.
# TODO: error reporting for lipo
if char 1 of item 10 of tFile is "7" and item 11 of tFile is not "rhapslnk" then
put format("lipo -remove ppc \"%s\" -output \"%s\"", pFolder & "/" & tFilename, pFolder & "/" & tFilename) into tCmd
log tCmd
get shell(tCmd)
if the result > 0 then
put format("lipo -remove ppc7400 \"%s\" -output \"%s\"", pFolder & "/" & tFilename, pFolder & "/" & tFilename) into tCmd
log tCmd
get shell(tCmd)
end if
end if
# Executables need to be signed.
if (_isMacOSExecutable(tFilename) or char 1 of item 10 of tFile is "7") and item 11 of tFile is not "rhapslnk" then
_signFile pFolder & "/" & tFilename, pCertificate
put the result into tError
end if
if tError is not empty then exit repeat
end repeat
end if
return tError for error
end _findBundlesAndStripExecutables
private function _folderListing pFilename
local tFolders, tFolder, tFullPathFolders
put folders(pFilename) into tFolders
if line 1 of tFolders is ".." then delete line 1 of tFolders
repeat for each line tFolder in tFolders
put pFilename & "/" & tFolder & cr after tFullPathFolders
end repeat
delete the last char of tFullPathFolders
return tFullPathFolders for value
end _folderListing
private function _fileListing pFilename
local tFiles, tFile, tFullPathFiles
put files(pFilename) into tFiles
# Packaging a macOS folder on Windows will complain about not finding the .DS_Store file
filter tFiles without ".DS_Store"
repeat for each line tFile in tFiles
put pFilename & "/" & tFile & cr after tFullPathFiles
end repeat
delete the last char of tFullPathFiles
return tFullPathFiles for value
end _fileListing
private function _resolveRelativeFilenameReference pFilename, pRootFolder
set the itemDelimiter to "/"
put _normalizeRelativeFilename(pFilename) into pFilename
repeat while pFilename begins with "../"
if pRootFolder is empty then
return "relative path is too deep for root folder" for error
else
delete the last item of pRootFolder
delete char 1 to 3 of pFilename
end if
end repeat
if pFilename is empty then
return pRootFolder for value
else
return pRootFolder & "/" & pFilename for value
end if
end _resolveRelativeFilenameReference
private function _normalizeRelativeFilename pFilename
if char 1 of pFilename is "/" then
delete char 1 of pFilename
else if char 1 to 2 of pFilename is "./" then
delete char 1 to 2 of pFilename
end if
return pFilename for value
end _normalizeRelativeFilename
private function _makeRelativePath pFilename, pRootFolder
local tIndex
local tMatchCount = 0
local tNonMatchCount =0
## NORMALIZE
if char 1 of pFilename is slash then delete char 1 of pFilename
if char 1 of pRootFolder is slash then delete char 1 of pRootFolder
if the last char of pFilename is slash then delete the last char of pFilename
if the last char of pRootFolder is slash then delete the last char of pRootFolder
set the itemDelimiter to slash
if pFilename is empty then return empty
if pRootFolder is empty then return empty
## IS pFilename A CHILD DIRECTORY OF pRootFolder?
if pRootFolder is item 1 to (the number of items of pRootFolder) of pFilename then
delete char 1 to length(pRootFolder) + 1 of pFilename
put "./" before pFilename
else if pFilename is char 1 to length(pFilename) of pRootFolder then
delete char 1 to length(pFilename) + 1 of pRootFolder
put empty into pFilename
repeat with tIndex = 1 to the number of items of pRootFolder
put "../" after pFilename
end repeat
else
## DETERMINE WHERE PATHS DIVERGE
repeat with tIndex = 1 to the number of items of pFilename
if item tIndex of pFilename is not item tIndex of pRootFolder then
put tIndex - 1 into tMatchCount
put the number of items of pRootFolder - tIndex + 1 into tNonMatchCount
exit repeat
end if
end repeat
if tMatchCount > 0 then
delete item 1 to tMatchCount of pFilename
end if
repeat with tIndex = 1 to tNonMatchCount
put "../" before pFilename
end repeat
end if
return pFilename
end _makeRelativePath
private function _qstr pStr
replace "`" with quote in pStr
return pStr
end _qstr
private command _fileCopyFile pSrcFile, pDestFile
local tShellError, tTouchResult
local tError, tResult
if the platform is "MacOS" then
put _escapeForShell(pSrcFile) into pSrcFile
put _escapeForShell(pDestFile) into pDestFile
put shell ("cp -f" && pSrcFile && pDestFile) into tResult ## removed -p as it had problems with servers
put the result into tShellError
if tShellError is empty then
## Copying to USB (10.5.2) wouldn't return file mod time when queried with detailed files
## This fixes it
put shell ("touch -c -a" && pDestFile) into tTouchResult
end if
else if the platform is "Win32" then
put _escapeForShell(pSrcFile) into pSrcFile
put _escapeForShell(pDestFile) into pDestFile
put shell ("copy /Y /V" && pSrcFile && pDestFile) into tResult ## dies on long filenames
put the result into tShellError
else
put _escapeForShell(pSrcFile) into pSrcFile
put _escapeForShell(pDestFile) into pDestFile
put shell ("cp -f" && pSrcFile && pDestFile) into tResult
put the result into tShellError
end if
if tShellError is not empty then
if tResult is not empty then
put line 1 to 3 of tResult into tError
else
put tShellError into tError
end if
end if
return tError
end _fileCopyFile
private command _fileCopyFolder pSrcFolder, pDestFolder
local tError, tLastItemofSrcFolder, tResult
if the platform is "MacOS" then
## ditto copies contents of pSrcFolder to pDestFolder if pDestFolder exists
## What we want is for pSrcFolder to exist within pDestFolder
if there is a directory pDestFolder then
set the itemdel to "/"
if char -1 of pDestFolder is "/" then delete char -1 of pDestFolder
put item -1 of pSrcFolder into tLastItemofSrcFolder
put "/" & tLastItemofSrcFolder after pDestFolder
create directory pDestFolder
end if
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
put shell ("ditto" && pSrcFolder && pDestFolder) into tResult
if tResult contains "operation not permitted" then
# macOS sandbox may not let us copy some metadata.
# Don't know why but it happens. Saw it when copying from temp folder to temp folder.
put shell ("ditto --norsrc" && pSrcFolder && pDestFolder) into tResult
end if
else if the platform is "Win32" then
if there is a directory pDestFolder then
set the itemdel to "/"
if char -1 of pDestFolder is "/" then delete char -1 of pDestFolder
put item -1 of pSrcFolder into tLastItemofSrcFolder
put "/" & tLastItemofSrcFolder after pDestFolder
create directory pDestFolder
end if
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
put shell ("robocopy" && pSrcFolder && pDestFolder && "/E /NFL /NDL /NS /NC /NJH /NJS") into tResult
else
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
put shell ("cp -rf" && pSrcFolder && pDestFolder) into tResult
end if
if the result is not empty then
put line 1 of tResult into tError
end if
return tError
end _fileCopyFolder
command packagerCreateAllFoldersInPath pPath, pRootPath
local tCheck, tError, tPathSegment
## Watch for double slashes /Folder/To//Something//
## You will not enter a never ending loop if you aren't careful.
_stripDoubleSlash pPath
_stripDoubleSlash pRootPath
## Get rid of trailing slashes
## We can safely ignore UNC paths starting with "//"
## Neither pPath or pRootPath with values of just "//" would be valid
repeat until the last char of pPath is not slash
delete the last char of pPath
end repeat
if the number of chars of pRootPath > 1 and the last char of pRootPath is slash then
repeat forever
delete the last char of pRootPath
if the last char of pRootPath is not slash or the number of chars of pRootPath is 1 then
exit repeat
end if
end repeat
end if
set the itemDelimiter to slash
if pPath is empty or the number of items of pPath is 1 then
put "cannot create folder (invalid path)" into tError
end if
## VALIDATE pRootPath
if tError is empty then
if pRootPath is empty then
put item 1 to 2 of pPath into pRootPath ## "/NODE"
end if
if tError is empty then
if last char of pRootPath is not slash then put slash after pRootPath ## Makes it easier to deal with "/" path
if not _fileIsFolder(pRootPath) then
put "root path does not exist [root:" && pRootPath & "]" into tError
end if
end if
end if
## VALIDATE ANCESTORY OF PATH
if tError is empty then
put char 1 to -2 of pRootPath into tCheck ## -2 gets rid of trailing slash
if char 1 to (number of chars of tCheck) of pPath is not tCheck then
put "path is not a child of root path" & cr & "[path:" && pPath & "]" & cr & "[root:" && pRootPath & "]" into tError
put the executioncontexts
end if
end if
## CREATE FOLDERS
if tError is empty then
if number of items of pPath > number of items of pRootPath then
put pRootPath & item (number of items of pRootPath + 1) of pPath into tPathSegment
if not _fileIsFolder(tPathSegment) then
create folder tPathSegment
if the result is not empty then
put "error creating folder (" & the result & ") [folder:" && tPathSegment & "]" into tError
end if
end if
if tError is empty then
packagerCreateAllFoldersInPath pPath, tPathSegment
put the result into tError
end if
end if
end if
return tError
end packagerCreateAllFoldersInPath
private function _fileIsFolder pPath
local tFolder, isAFolder
put the defaultFolder into tFolder
set the defaultfolder to pPath
put the result is empty into isAFolder
set the defaultFolder to tFolder
return isAFolder
end _fileIsFolder
private command _stripDoubleSlash @pVariable
local tCharNo, tPrefix
# Don't wipe out UNC prefixes
if char 1 to 2 of pVariable is "//" then
put "//" into tPrefix
delete char 1 to 2 of pVariable
end if
repeat forever
put offset("//", pVariable) into tCharNo
if tCharNo > 0 then
replace "//" with slash in pVariable
else
exit repeat
end if
end repeat
if tPrefix is not empty then
put tPrefix before pVariable
end if
return empty
end _stripDoubleSlash
private function _escapeForShell pStr
local tChar, tSpecialChars
if the platform is "win32" then
replace "/" with "\" in pStr
put quote & pStr & quote into pStr
else
put "\" & space & quote & "'`<>!;()[]?#$^&*=|" into tSpecialChars
repeat for each char tChar in tSpecialChars
replace tChar with ("\" & tChar) in pStr
end repeat
end if
return pStr
end _escapeForShell
on _moveFolder pSrcFolder, pDestFolder
local tError,tResult, tExitCode
if the platform is "MacOS" then
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
put shell ("mv -f" && pSrcFolder && pDestFolder) into tResult
put the result into tExitCode
else if the platform is "Win32" then
if the last char of pSrcFolder is "/" then delete the last char of pSrcFolder
if the last char of pDestFolder is "/" then delete the last char of pDestFolder
set the itemDelimiter to slash
put "/" & the last item of pSrcFolder after pDestFolder
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
# Note: `move` fails on networks paths like \\mypath\file.
# Use `robocopy` or `xcopy` instead.
if _isWindowsVistaOrNewer() then
## VISTA INTRODUCED ROBOCOPY. XCOPY WAS FAILING WITH COPIES TO PARALLELS NETWORK SHARE
put shell ("robocopy" && pSrcFolder && pDestFolder && "/e /nfl /ndl /ns /nc /njh /njs") into tResult
else
put shell ("xcopy" && pSrcFolder && pDestFolder && "/y /v /i /e /r /h") into tResult
end if
put the result into tExitCode
if tExitCode is empty then ## exit code is number of affected files
revDeleteFolder pSrcFolder
end if
else -- linux
put _escapeForShell(pSrcFolder) into pSrcFolder
put _escapeForShell(pDestFolder) into pDestFolder
put shell ("mv -f" && pSrcFolder && pDestFolder) into tResult
put the result into tExitCode
end if
if tExitCode is not empty then
put line 1 of tResult into tError
end if
return tError
end _moveFolder
## Like revDeleteFolder but returns valid error message.
on _deleteFolder pSrcFolder
if the platform is "MacOS" and char 1 of the systemVersion is not "1" then
if "applescript" is in the alternateLanguages then
# OK-2009-02-11 : Removed unquoted literal pDestFolder
--do revAppleScriptFull("deleteFolder",pSrcFolder,pDestFolder) as applescript
do revAppleScriptFull("deleteFolder", pSrcFolder) as "applescript"
return the result
else return "Error: AppleScript not installed"
else if the platform is "Win32" then
revSetWindowsShellCommand
if _isWindowsVistaOrNewer() then
get shell ("rmdir /s /q" && revWindowsFromUnixPath(quote&pSrcFolder"e))
else
get shell ("deltree /Y" && revWindowsFromUnixPath(quote&pSrcFolder"e))
end if
else
get shell ("rm -rf" && quote&pSrcFolder"e)
end if
## Only applies to Win/Linux
if the result is not empty then return it
else return empty
end _deleteFolder
private function _isWindowsVistaOrNewer
return word 1 of the systemVersion is "NT" and word 2 of the systemVersion >= 6.0
end _isWindowsVistaOrNewer
private function _fileExtractName pFilePath
set the itemDelimiter to slash
return the last item of pFilePath
end _fileExtractName
private function _printArray pArray, pDimension, pFullData
local tKeys, tKey, tText, tTempArray
if pDimension is empty then put 0 into pDimension
put the keys of pArray into tKeys
sort tKeys numeric
repeat for each line tKey in tKeys
if pArray[tKey] is an array then
put _printCharXTimes(space, pDimension * 3) & tKey & cr after tText
put pArray[tKey] into tTempArray
put _printArray(tTempArray, pDimension + 1, pFullData) after tText
else
if pFullData then
put _printCharXTimes(space, pDimension * 3) & tKey & ":" && pArray[tKey] & cr after tText
else
put _printCharXTimes(space, pDimension * 3) & tKey & ":" && line 1 of pArray[tKey] & cr after tText
end if
end if
end repeat
return tText
end _printArray
private function _printCharXTimes pChar, pTimes
local tStr
repeat with i = 1 to pTimes
put pChar after tStr
end repeat
return tStr
end _printCharXTimes
/*
To run the packager on the command line of an installed IDE use:
```
\
-ui \
\
\
```
For example:
```
"/Applications/LiveCode 9.6.8.app/Contents/MacOS/LiveCode" \
-ui \
~/myapp/levure/packager/packager.livecodescript \
~/myapp/app/standalone.livecode \
beta
```
*/
on startup
_LoadHomeStack
start using stack "revSBLibrary"
if there is no stack $1 then
write "Stack not found" && $1 & return to stderr
quit 1
end if
go stack $1
put "running" into sCommandLineStatus
send "_doPackageCommand $1, $2" \
to me \
in 100 milliseconds
/* There is a send in time in levurePackageApplication so we need to wait
* here until complete */
wait while sCommandLineStatus is "running" with messages
switch sCommandLineStatus
case "complete"
/* completed ok but there may be warnings */
if revStandaloneGetWarnings() is not empty then
write revStandaloneGetWarnings() & return to stdout
end if
quit 0
case "failed"
quit 2
case "cancelled"
quit 3
default
write "unknown command line status" && sCommandLineStatus & return to stderr
quit 99
end switch
end startup
on _doPackageCommand \
pStandaloneStack, \
pBuildProfile
send "levurePackageApplication pBuildProfile" to stack pStandaloneStack
end _doPackageCommand
private function _IsInstalledIDE
set the itemDelimiter to slash
switch the platform
case "MacOS"
local tBundleContents
put item 1 to -2 of specialFolderPath("engine") into tBundleContents
if there is a folder (tBundleContents & "/Tools/Toolset") then
return true
end if
return false
case "Win32"
local tBinFolder
put specialFolderPath("engine") into tBinFolder
return there is a file tBinFolder & slash & "revxml.dll"
end switch
end _IsInstalledIDE
private command _LoadHomeStack
set the itemDelimiter to slash
local tToolsetLocation
if _IsInstalledIDE() then
switch the platform
case "MacOS"
local tBundleContents
put item 1 to -2 of specialFolderPath("engine") into tBundleContents
put tBundleContents & "/Tools/Toolset" \
into tToolsetLocation
break
case "Win32"
write "Cannot load standalone libraries" & return to stderr
quit 1
end switch
else
local tRepoFolder
switch the platform
case "MacOS"
if specialFolderPath("engine") contains "_build" then
put item 1 to -7 of specialFolderPath("engine") into tRepoFolder
else
put item 1 to -5 of specialFolderPath("engine") into tRepoFolder
end if
break
case "Win32"
put item 1 to -2 of specialFolderPath("engine") into tRepoFolder
break
end switch
put tRepoFolder & "/ide/Toolset" into tToolsetLocation
end if
local tHome
put tToolsetLocation & "/home.livecodescript" into tHome
-- Set the 'test environment' to true
dispatch "revSetTestEnvironment" to stack tHome with true
insert the script of stack tHome into back
dispatch "startup" to stack tHome
open stack tHome
end _LoadHomeStack