2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2014-09-22 11:23:43 -04:00
using System.Text.RegularExpressions ;
2014-03-14 14:13:41 -04:00
using System.Linq ;
using System.Text ;
using System.IO ;
using AutomationTool ;
using UnrealBuildTool ;
2014-09-15 10:56:03 -04:00
using System.Diagnostics ;
using System.Threading ;
2014-03-14 14:13:41 -04:00
public class HTML5Platform : Platform
{
2014-09-22 11:23:43 -04:00
static string EmscriptenSettingsPath = CombinePaths ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) , "/.emscripten" ) ;
Dictionary < string , string > ReadEmscriptenSettings ( )
{
// Check HTML5ToolChain.cs
if ( ! System . IO . File . Exists ( EmscriptenSettingsPath ) )
{
return new Dictionary < string , string > ( ) ;
}
Dictionary < string , string > Settings = new Dictionary < string , string > ( ) ;
System . IO . StreamReader SettingFile = new System . IO . StreamReader ( EmscriptenSettingsPath ) ;
string EMLine = SettingFile . ReadToEnd ( ) ;
string Pattern = @"(\w+)\s*=\s*['\[]((?:[^'\\]|\\.^)*)['\]]" ;
Regex Rgx = new Regex ( Pattern , RegexOptions . IgnoreCase ) ;
MatchCollection Matches = Rgx . Matches ( EMLine ) ;
foreach ( Match Matched in Matches )
{
if ( Matched . Groups . Count = = 3 & & Matched . Groups [ 2 ] . ToString ( ) ! = "" )
{
Settings [ Matched . Groups [ 1 ] . ToString ( ) ] = Matched . Groups [ 2 ] . ToString ( ) ;
}
}
return Settings ;
}
2014-03-14 14:13:41 -04:00
public HTML5Platform ( )
2014-08-18 13:29:39 -04:00
: base ( UnrealTargetPlatform . HTML5 )
2014-03-14 14:13:41 -04:00
{
}
public override void Package ( ProjectParams Params , DeploymentContext SC , int WorkingCL )
{
2014-07-07 19:15:12 -04:00
Log ( "Package {0}" , Params . RawProjectPath ) ;
2014-09-22 11:23:43 -04:00
var EmscriptenSettings = ReadEmscriptenSettings ( ) ;
2014-07-07 19:15:12 -04:00
string PackagePath = Path . Combine ( Path . GetDirectoryName ( Params . RawProjectPath ) , "Binaries" , "HTML5" ) ;
if ( ! Directory . Exists ( PackagePath ) )
{
Directory . CreateDirectory ( PackagePath ) ;
}
2014-08-18 13:29:39 -04:00
string FinalDataLocation = Path . Combine ( PackagePath , Params . ShortProjectName ) + ".data" ;
2014-07-07 19:15:12 -04:00
// we need to operate in the root
using ( new PushedDirectory ( Path . Combine ( Params . BaseStageDirectory , "HTML5" ) ) )
{
2014-08-18 13:29:39 -04:00
string BaseSDKPath = Environment . GetEnvironmentVariable ( "EMSCRIPTEN" ) ;
2014-09-22 11:23:43 -04:00
string PythonPath = null ;
// Check the .emscripten file for a possible python path
if ( EmscriptenSettings . ContainsKey ( "PYTHON" ) )
{
PythonPath = EmscriptenSettings [ "PYTHON" ] ;
}
// The AutoSDK defines this env var as part of its install. See setup.bat/unsetup.bat
// If it's missing then just assume that python lives on the path
if ( PythonPath = = null )
{
PythonPath = Environment . GetEnvironmentVariable ( "PYTHON" ) ;
}
2014-08-18 13:29:39 -04:00
// make the file_packager command line
if ( Utils . IsRunningOnMono )
2014-07-07 19:15:12 -04:00
{
string PackagerPath = BaseSDKPath + "/tools/file_packager.py" ;
2014-09-22 11:23:43 -04:00
if ( PythonPath = = null )
{
string CmdLine = string . Format ( "-c \" python {0} '{1}' --preload . --js-output='{1}.js' \" " , PackagerPath , FinalDataLocation ) ;
RunAndLog ( CmdEnv , "/bin/bash" , CmdLine ) ;
}
else
{
string CmdLine = string . Format ( "{0} '{1}' --preload . --js-output='{1}.js' " , PackagerPath , FinalDataLocation ) ;
RunAndLog ( CmdEnv , PythonPath , CmdLine ) ;
}
2014-08-18 13:29:39 -04:00
}
else
{
2014-07-07 19:15:12 -04:00
string PackagerPath = "\"" + BaseSDKPath + "\\tools\\file_packager.py\"" ;
2014-09-22 11:23:43 -04:00
if ( PythonPath = = null )
{
string CmdLine = string . Format ( "/c python {0} \"{1}\" --preload . --js-output=\"{1}.js\"" , PackagerPath , FinalDataLocation ) ;
RunAndLog ( CmdEnv , CommandUtils . CombinePaths ( Environment . SystemDirectory , "cmd.exe" ) , CmdLine ) ;
}
else
{
string CmdLine = string . Format ( "{0} \"{1}\" --preload . --js-output=\"{1}.js\"" , PackagerPath , FinalDataLocation ) ;
RunAndLog ( CmdEnv , PythonPath , CmdLine ) ;
}
2014-07-07 19:15:12 -04:00
}
}
// copy the "Executable" to the package directory
string GameExe = Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) ;
if ( Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ! = "Development" )
{
GameExe + = "-HTML5-" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ;
}
GameExe + = ".js" ;
if ( Path . Combine ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , GameExe ) ! = Path . Combine ( PackagePath , GameExe ) )
{
File . Copy ( Path . Combine ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , GameExe ) , Path . Combine ( PackagePath , GameExe ) , true ) ;
File . Copy ( Path . Combine ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , GameExe ) + ".mem" , Path . Combine ( PackagePath , GameExe ) + ".mem" , true ) ;
}
File . SetAttributes ( Path . Combine ( PackagePath , GameExe ) , FileAttributes . Normal ) ;
File . SetAttributes ( Path . Combine ( PackagePath , GameExe ) + ".mem" , FileAttributes . Normal ) ;
2014-08-18 13:29:39 -04:00
2014-07-07 19:15:12 -04:00
// put the HTML file to the package directory
string TemplateFile = Path . Combine ( CombinePaths ( CmdEnv . LocalRoot , "Engine" ) , "Build" , "HTML5" , "Game.html.template" ) ;
string OutputFile = Path . Combine ( PackagePath , ( Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ! = "Development" ? ( Params . ShortProjectName + "-HTML5-" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ) : Params . ShortProjectName ) ) + ".html" ;
2014-08-18 13:29:39 -04:00
2014-07-07 19:15:12 -04:00
// find Heap Size.
ulong HeapSize ;
2014-08-18 13:29:39 -04:00
var ConfigCache = new UnrealBuildTool . ConfigCacheIni ( UnrealTargetPlatform . HTML5 , "Engine" , Path . GetDirectoryName ( Params . RawProjectPath ) , CombinePaths ( CmdEnv . LocalRoot , "Engine" ) ) ;
2014-07-07 19:15:12 -04:00
2014-08-18 13:29:39 -04:00
int ConfigHeapSize ;
if ( ! ConfigCache . GetInt32 ( "BuildSettings" , "HeapSize" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) , out ConfigHeapSize ) ) // in Megs.
{
2014-07-07 19:15:12 -04:00
// we couldn't find a per config heap size, look for a common one.
2014-08-18 13:29:39 -04:00
if ( ! ConfigCache . GetInt32 ( "BuildSettings" , "HeapSize" , out ConfigHeapSize ) )
{
ConfigHeapSize = Params . IsCodeBasedProject ? 1024 : 512 ;
Log ( "Could not find Heap Size setting in .ini for Client config {0}" , Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ) ;
}
2014-07-07 19:15:12 -04:00
}
2014-09-15 10:56:03 -04:00
HeapSize = ( ulong ) ConfigHeapSize * 1024L * 1024L ; // convert to bytes.
2014-08-18 13:29:39 -04:00
Log ( "Setting Heap size to {0} Mb " , ConfigHeapSize ) ;
2014-07-07 19:15:12 -04:00
GenerateFileFromTemplate ( TemplateFile , OutputFile , Params . ShortProjectName , Params . ClientConfigsToBuild [ 0 ] . ToString ( ) , Params . StageCommandline , ! Params . IsCodeBasedProject , HeapSize ) ;
// copy the jstorage files to the binaries directory
string JSDir = Path . Combine ( CombinePaths ( CmdEnv . LocalRoot , "Engine" ) , "Build" , "HTML5" ) ;
string OutDir = PackagePath ;
File . Copy ( JSDir + "/json2.js" , OutDir + "/json2.js" , true ) ;
File . SetAttributes ( OutDir + "/json2.js" , FileAttributes . Normal ) ;
File . Copy ( JSDir + "/jstorage.js" , OutDir + "/jstorage.js" , true ) ;
File . SetAttributes ( OutDir + "/jstorage.js" , FileAttributes . Normal ) ;
File . Copy ( JSDir + "/moz_binarystring.js" , OutDir + "/moz_binarystring.js" , true ) ;
File . SetAttributes ( OutDir + "/moz_binarystring.js" , FileAttributes . Normal ) ;
2014-03-14 14:13:41 -04:00
PrintRunTime ( ) ;
}
2014-09-22 14:24:24 -04:00
public override bool RequiresPackageToDeploy
{
get { return true ; }
}
2014-05-29 17:32:36 -04:00
protected void GenerateFileFromTemplate ( string InTemplateFile , string InOutputFile , string InGameName , string InGameConfiguration , string InArguments , bool IsContentOnly , ulong HeapSize )
2014-03-14 14:13:41 -04:00
{
StringBuilder outputContents = new StringBuilder ( ) ;
using ( StreamReader reader = new StreamReader ( InTemplateFile ) )
{
string LineStr = null ;
while ( reader . Peek ( ) ! = - 1 )
{
LineStr = reader . ReadLine ( ) ;
if ( LineStr . Contains ( "%GAME%" ) )
{
LineStr = LineStr . Replace ( "%GAME%" , InGameName ) ;
}
2014-08-18 13:29:39 -04:00
if ( LineStr . Contains ( "%HEAPSIZE%" ) )
2014-05-29 17:32:36 -04:00
{
2014-08-18 13:29:39 -04:00
LineStr = LineStr . Replace ( "%HEAPSIZE%" , HeapSize . ToString ( ) ) ;
2014-05-29 17:32:36 -04:00
}
2014-03-14 14:13:41 -04:00
if ( LineStr . Contains ( "%CONFIG%" ) )
{
2014-06-18 11:36:29 -04:00
if ( IsContentOnly )
InGameName = "UE4Game" ;
2014-04-23 18:20:36 -04:00
LineStr = LineStr . Replace ( "%CONFIG%" , ( InGameConfiguration ! = "Development" ? ( InGameName + "-HTML5-" + InGameConfiguration ) : InGameName ) ) ;
2014-03-14 14:13:41 -04:00
}
if ( LineStr . Contains ( "%UE4CMDLINE%" ) )
{
2014-09-15 10:56:03 -04:00
InArguments = InArguments . Replace ( "\"" , "" ) ;
2014-03-14 14:13:41 -04:00
string [ ] Arguments = InArguments . Split ( ' ' ) ;
2014-08-18 13:29:39 -04:00
string ArgumentString = IsContentOnly ? "'" + InGameName + "/" + InGameName + ".uproject '," : "" ;
for ( int i = 0 ; i < Arguments . Length - 1 ; + + i )
2014-03-14 14:13:41 -04:00
{
ArgumentString + = "'" ;
2014-09-15 10:56:03 -04:00
ArgumentString + = Arguments [ i ] ;
2014-03-14 14:13:41 -04:00
ArgumentString + = "'" ;
ArgumentString + = ",' '," ;
}
if ( Arguments . Length > 0 )
{
ArgumentString + = "'" ;
2014-08-18 13:29:39 -04:00
ArgumentString + = Arguments [ Arguments . Length - 1 ] ;
2014-03-14 14:13:41 -04:00
ArgumentString + = "'" ;
}
LineStr = LineStr . Replace ( "%UE4CMDLINE%" , ArgumentString ) ;
}
outputContents . AppendLine ( LineStr ) ;
}
}
if ( outputContents . Length > 0 )
{
// Save the file
try
{
Directory . CreateDirectory ( Path . GetDirectoryName ( InOutputFile ) ) ;
File . WriteAllText ( InOutputFile , outputContents . ToString ( ) , Encoding . UTF8 ) ;
}
catch ( Exception )
{
// Unable to write to the project file.
}
}
}
public override void GetFilesToDeployOrStage ( ProjectParams Params , DeploymentContext SC )
{
}
2014-07-07 19:15:12 -04:00
public override void GetFilesToArchive ( ProjectParams Params , DeploymentContext SC )
{
if ( SC . StageTargetConfigurations . Count ! = 1 )
{
throw new AutomationException ( "iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations" , SC . StageTargetConfigurations . Count ) ;
}
string PackagePath = Path . Combine ( Path . GetDirectoryName ( Params . RawProjectPath ) , "Binaries" , "HTML5" ) ;
string FinalDataLocation = Path . Combine ( PackagePath , Params . ShortProjectName ) + ".data" ;
// copy the "Executable" to the archive directory
string GameExe = Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) ;
if ( Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ! = "Development" )
{
GameExe + = "-HTML5-" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ;
}
GameExe + = ".js" ;
// put the HTML file to the package directory
string OutputFile = Path . Combine ( PackagePath , ( Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ! = "Development" ? ( Params . ShortProjectName + "-HTML5-" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ) : Params . ShortProjectName ) ) + ".html" ;
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( FinalDataLocation ) ) ;
2014-08-18 13:29:39 -04:00
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( FinalDataLocation + ".js" ) ) ;
2014-07-07 19:15:12 -04:00
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( GameExe ) ) ;
2014-08-18 13:29:39 -04:00
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( GameExe + ".mem" ) ) ;
2014-07-07 19:15:12 -04:00
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( "json2.js" ) ) ;
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( "jstorage.js" ) ) ;
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( "moz_binarystring.js" ) ) ;
SC . ArchiveFiles ( PackagePath , Path . GetFileName ( OutputFile ) ) ;
}
2014-03-14 14:13:41 -04:00
public override ProcessResult RunClient ( ERunOptions ClientRunFlags , string ClientApp , string ClientCmdLine , ProjectParams Params )
{
2014-09-02 14:36:52 -04:00
// look for browser
var ConfigCache = new UnrealBuildTool . ConfigCacheIni ( UnrealTargetPlatform . HTML5 , "Engine" , Path . GetDirectoryName ( Params . RawProjectPath ) , CombinePaths ( CmdEnv . LocalRoot , "Engine" ) ) ;
2014-03-14 14:13:41 -04:00
2014-09-15 10:56:03 -04:00
string DeviceSection ;
2014-03-14 14:13:41 -04:00
2014-09-02 14:36:52 -04:00
if ( Utils . IsRunningOnMono )
{
DeviceSection = "HTML5DevicesMac" ;
}
else
{
DeviceSection = "HTML5DevicesWindows" ;
}
2014-03-14 14:13:41 -04:00
2014-09-02 14:36:52 -04:00
string browserPath ;
string DeviceName = Params . Device . Split ( '@' ) [ 1 ] ;
2014-09-23 09:14:00 -04:00
DeviceName = DeviceName . Substring ( 0 , DeviceName . LastIndexOf ( " on " ) ) ;
2014-09-02 14:36:52 -04:00
bool ok = ConfigCache . GetString ( DeviceSection , DeviceName , out browserPath ) ;
2014-09-15 10:56:03 -04:00
2014-09-02 14:36:52 -04:00
if ( ! ok )
2014-09-15 10:56:03 -04:00
throw new System . Exception ( "Incorrect browser configuration in HTML5Engine.ini " ) ;
2014-03-14 14:13:41 -04:00
2014-07-21 17:06:23 -04:00
// open the webpage
2014-09-02 14:36:52 -04:00
string directory = Path . GetDirectoryName ( ClientApp ) ;
2014-09-15 10:56:03 -04:00
string url = Path . GetFileName ( ClientApp ) + ".html" ;
// Are we running via cook on the fly server?
// find our http url - This is awkward because RunClient doesn't have real information that NFS is running or not.
2014-08-18 13:29:39 -04:00
bool IsCookOnTheFly = false ;
string [ ] Arguments = ClientCmdLine . Split ( ' ' ) ;
foreach ( var Argument in Arguments )
{
string [ ] KeyVal = Argument . Split ( '=' ) ;
if ( KeyVal [ 0 ] . ToLower ( ) . Contains ( "filehostip" ) )
{
2014-09-15 10:56:03 -04:00
// split urls.
2014-08-18 13:29:39 -04:00
string [ ] Urls = KeyVal [ 1 ] . Split ( '+' ) ;
foreach ( var Url in Urls )
{
if ( Url . Contains ( "http" ) )
{
2014-09-02 14:36:52 -04:00
url = Url + "/" + Path . GetFileName ( url ) ;
2014-08-18 13:29:39 -04:00
IsCookOnTheFly = true ;
}
}
}
if ( IsCookOnTheFly )
break ;
}
2014-07-21 17:06:23 -04:00
2014-09-02 14:36:52 -04:00
if ( IsCookOnTheFly )
{
url + = "?cookonthefly=true" ;
}
else
{
2014-09-23 09:14:00 -04:00
var EmscriptenSettings = ReadEmscriptenSettings ( ) ;
2014-09-02 14:36:52 -04:00
url = "http://127.0.0.1:8000/" + url ;
2014-09-15 10:56:03 -04:00
// this will be killed UBT instances dies.
2014-09-02 14:36:52 -04:00
string input = String . Format ( " -m SimpleHTTPServer 8000" ) ;
2014-07-24 19:26:40 -04:00
2014-03-14 14:13:41 -04:00
2014-09-23 09:14:00 -04:00
string PythonName = null ;
// Check the .emscripten file for a possible python path
if ( EmscriptenSettings . ContainsKey ( "PYTHON" ) )
{
PythonName = EmscriptenSettings [ "PYTHON" ] ;
}
// The AutoSDK defines this env var as part of its install. See setup.bat/unsetup.bat
// If it's missing then just assume that python lives on the path
if ( PythonName = = null )
{
PythonName = Environment . GetEnvironmentVariable ( "PYTHON" ) ;
}
if ( PythonName = = null )
{
PythonName = Utils . IsRunningOnMono ? "python" : "python.exe" ;
}
2014-09-02 14:36:52 -04:00
ProcessResult Result = ProcessManager . CreateProcess ( PythonName , true , "html5server.log" ) ;
Result . ProcessObject . StartInfo . FileName = PythonName ;
2014-09-15 10:56:03 -04:00
Result . ProcessObject . StartInfo . UseShellExecute = false ;
2014-09-02 14:36:52 -04:00
Result . ProcessObject . StartInfo . RedirectStandardOutput = true ;
Result . ProcessObject . StartInfo . RedirectStandardInput = true ;
Result . ProcessObject . StartInfo . WorkingDirectory = directory ;
Result . ProcessObject . StartInfo . Arguments = input ;
Result . ProcessObject . Start ( ) ;
Result . ProcessObject . OutputDataReceived + = delegate ( object sender , System . Diagnostics . DataReceivedEventArgs e )
{
System . Console . WriteLine ( e . Data ) ;
} ;
2014-09-15 10:56:03 -04:00
System . Console . WriteLine ( "Starting Browser Process" ) ;
2014-09-02 14:36:52 -04:00
2014-09-15 10:56:03 -04:00
// safari specific hack.
string argument = url ;
if ( browserPath . Contains ( "Safari" ) & & Utils . IsRunningOnMono )
argument = "" ;
ProcessResult ClientProcess = Run ( browserPath , argument , null , ClientRunFlags | ERunOptions . NoWaitForExit ) ;
ClientProcess . ProcessObject . EnableRaisingEvents = true ;
2014-09-02 14:36:52 -04:00
ClientProcess . ProcessObject . Exited + = delegate ( System . Object o , System . EventArgs e )
{
System . Console . WriteLine ( "Browser Process Ended - Killing Webserver" ) ;
2014-09-15 10:56:03 -04:00
// send kill.
2014-09-02 14:36:52 -04:00
Result . ProcessObject . StandardInput . Close ( ) ;
Result . ProcessObject . Kill ( ) ;
} ;
2014-09-15 10:56:03 -04:00
// safari needs a hack.
// http://superuser.com/questions/689315/run-safari-from-terminal-with-given-url-address-without-open-command
if ( browserPath . Contains ( "Safari" ) & & Utils . IsRunningOnMono )
{
// ClientProcess.ProcessObject.WaitForInputIdle ();
Thread . Sleep ( 2000 ) ;
Process . Start ( "/usr/bin/osascript" , " -e 'tell application \"Safari\" to open location \"" + url + "\"'" ) ;
}
2014-09-02 14:36:52 -04:00
return ClientProcess ;
}
System . Console . WriteLine ( "Browser Path " + browserPath ) ;
ProcessResult BrowserProcess = Run ( browserPath , url , null , ClientRunFlags | ERunOptions . NoWaitForExit ) ;
2014-09-15 10:56:03 -04:00
return BrowserProcess ;
2014-09-02 14:36:52 -04:00
2014-03-14 14:13:41 -04:00
}
public override string GetCookPlatform ( bool bDedicatedServer , bool bIsClientOnly , string CookFlavor )
{
return "HTML5" ;
}
public override bool DeployPakInternalLowerCaseFilenames ( )
{
return false ;
}
public override bool DeployLowerCaseFilenames ( bool bUFSFile )
{
return false ;
}
public override string LocalPathToTargetPath ( string LocalPath , string LocalRoot )
{
return LocalPath ; //.Replace("\\", "/").Replace(LocalRoot, "../../..");
}
public override bool IsSupported { get { return true ; } }
2014-08-18 13:29:39 -04:00
public override List < string > GetDebugFileExtentions ( )
{
return new List < string > { ".pdb" } ;
}
2014-03-14 14:13:41 -04:00
#region Hooks
public override void PreBuildAgenda ( UE4Build Build , UE4Build . BuildAgenda Agenda )
{
}
public override List < string > GetExecutableNames ( DeploymentContext SC , bool bIsRun = false )
{
var ExecutableNames = new List < String > ( ) ;
ExecutableNames . Add ( Path . Combine ( SC . ProjectRoot , "Binaries" , "HTML5" , SC . ShortProjectName ) ) ;
return ExecutableNames ;
}
#endregion
}