// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Threading; using System.Reflection; using AutomationTool; using UnrealBuildTool; [Help(@"Builds engine documentation.")] [Help("-env", "Builds the compile environment")] public class BuildDocumentation : BuildCommand { public override void ExecuteBuild() { // Parse the command line bool bClean = ParseParam("clean"); bool bCleanMeta = bClean || ParseParam("cleanmeta"); bool bCleanEnv = bClean || ParseParam("cleanenv"); bool bCleanXml = bClean || ParseParam("cleanxml"); bool bCleanUdn = bClean || ParseParam("cleanudn"); bool bCleanHtml = bClean || ParseParam("cleanhtml"); bool bCleanChm = bClean || ParseParam("cleanchm"); bool bBuild = ParseParam("build"); bool bBuildMeta = bBuild || ParseParam("buildmeta"); bool bBuildEnv = bBuild || ParseParam("buildenv"); bool bBuildXml = bBuild || ParseParam("buildxml"); bool bBuildUdn = bBuild || ParseParam("buildudn"); bool bBuildHtml = bBuild || ParseParam("buildhtml"); bool bBuildChm = bBuild || ParseParam("buildchm"); if(ParseParam("nodoxygen")) { bCleanMeta = bCleanEnv = bCleanXml = bBuildMeta = bBuildEnv = bBuildXml = false; } bool bStats = ParseParam("stats"); bool bMakeArchives = ParseParam("archive"); string Filter = ParseParamValue("filter"); // Create the intermediate folders string IntermediateDir = Path.Combine(CmdEnv.LocalRoot, "Engine\\Intermediate\\Documentation"); CommandUtils.CreateDirectory(IntermediateDir); string BuildDir = Path.Combine(IntermediateDir, "build"); CommandUtils.CreateDirectory(BuildDir); string DoxygenDir = Path.Combine(IntermediateDir, "doxygen"); CommandUtils.CreateDirectory(DoxygenDir); string MetadataDir = Path.Combine(IntermediateDir, "metadata"); CommandUtils.CreateDirectory(MetadataDir); string ArchiveDir = Path.Combine(CmdEnv.LocalRoot, "Engine\\Documentation\\Builds"); CommandUtils.CreateDirectory(ArchiveDir); // Get all the file paths string TargetInfoPath = Path.Combine(BuildDir, "targetinfo.xml"); string UnrealBuildToolProject = Path.Combine(CmdEnv.LocalRoot, "Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.csproj"); string ApiDocToolSolution = Path.Combine(CmdEnv.LocalRoot, "Engine/Source/Programs/UnrealDocTool/APIDocTool/APIDocTool.sln"); string ApiDocToolPath = Path.Combine(CmdEnv.LocalRoot, "Engine/Source/Programs/UnrealDocTool/APIDocTool/APIDocTool/bin/x64/Release/APIDocTool.exe"); string UnrealDocToolSolution = Path.Combine(CmdEnv.LocalRoot, "Engine/Source/Programs/UnrealDocTool/UnrealDocTool/UnrealDocTool.sln"); string StatsPath = "\\\\epicgames.net\\root\\UE3\\UE4\\Documentation\\API-Stats.txt"; // Compile UnrealBuildTool MsBuild(CmdEnv, UnrealBuildToolProject, "/verbosity:normal /target:Rebuild /property:Configuration=Development /property:Platform=AnyCPU", "BuildUnrealBuildTool"); // Compile APIDocTool BuildSolution(CmdEnv, ApiDocToolSolution, "Release|x64", "BuildApiDocTool"); // Compile UnrealDocTool BuildSolution(CmdEnv, UnrealDocToolSolution, "Release", "BuildUnrealDocTool"); // Prepare the shared doc tool command line, containing all the paths string ApiToolCommandLine = ""; ApiToolCommandLine += " -enginedir=\"" + Path.Combine(CmdEnv.LocalRoot, "Engine") + "\""; ApiToolCommandLine += " -targetinfo=\"" + TargetInfoPath + "\""; ApiToolCommandLine += " -xmldir=\"" + DoxygenDir + "\""; ApiToolCommandLine += " -metadatadir=\"" + MetadataDir + "\""; ApiToolCommandLine += " -verbose"; if (Filter != null) ApiToolCommandLine += " -filter=" + Filter; // Execute the clean if(bCleanEnv) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleantarget" + ApiToolCommandLine, "ApiDocTool-CleanTargetInfo"); } if (bCleanMeta) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleanmeta" + ApiToolCommandLine, "ApiDocTool-CleanMeta"); } if(bCleanXml) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleanxml" + ApiToolCommandLine, "ApiDocTool-CleanXML"); } if(bCleanUdn) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleanudn" + ApiToolCommandLine, "ApiDocTool-CleanUDN"); } if(bCleanHtml) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleanhtml" + ApiToolCommandLine, "ApiDocTool-CleanHTML"); } if (bCleanChm) { RunAndLog(CmdEnv, ApiDocToolPath, "-cleanchm" + ApiToolCommandLine, "ApiDocTool-CleanCHM"); } // Execute the build if (bBuildEnv) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildtarget" + ApiToolCommandLine, "ApiDocTool-BuildTargetInfo"); } if (bBuildMeta) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildmeta" + ApiToolCommandLine, "ApiDocTool-BuildMeta"); } if (bBuildXml) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildxml" + ApiToolCommandLine, "ApiDocTool-BuildXML"); if (bMakeArchives) { CreateAndSubmitArchiveFromDir("Engine/Documentation/Builds/API-XML.tgz", "API documentation intermediates", IntermediateDir); } } if (bBuildUdn) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildudn" + (bStats? (" -stats=\"" + StatsPath + "\"") : "") + ApiToolCommandLine, "ApiDocTool-BuildUDN"); if (bMakeArchives) { CreateAndSubmitArchiveFromDir("Engine/Documentation/Builds/API-UDN.tgz", "API documentation UDN output", Path.Combine(CmdEnv.LocalRoot, "Engine\\Documentation\\Source\\API")); CreateAndSubmitArchiveFromFiles("Engine/Documentation/Builds/API-Sitemap.tgz", "API documentation sitemap output", Path.Combine(CmdEnv.LocalRoot, "Engine\\Documentation\\CHM"), new string[] { "API.hhc", "API.hhk" }); } } if (bBuildHtml) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildhtml" + ApiToolCommandLine, "ApiDocTool-BuildHTML"); if (bMakeArchives) { string BaseDir = Path.Combine(CmdEnv.LocalRoot, "Engine\\Documentation\\HTML"); List InputFiles = new List(); FindFilesForTar(BaseDir, "Include", InputFiles, true); FindFilesForTar(BaseDir, "INT\\API", InputFiles, true); FindFilesForTar(BaseDir, "Images", InputFiles, false); CommandUtils.CopyFile(Path.Combine(CmdEnv.LocalRoot, "Engine\\Documentation\\Extras\\API\\index.html"), Path.Combine(BaseDir, "index.html")); InputFiles.Add("index.html"); CreateAndSubmitArchiveFromFiles("Engine/Documentation/Builds/API-HTML.tgz", "API documentation HTML output", BaseDir, InputFiles); } } if (bBuildChm) { RunAndLog(CmdEnv, ApiDocToolPath, "-buildchm" + ApiToolCommandLine, "ApiDocTool-BuildCHM"); SubmitFile("Engine/Documentation/CHM/API.chm", "API documentation CHM output"); } } static void CreateAndSubmitArchiveFromDir(string DepotArchivePath, string Description, string BaseDir) { // Find all the input files List InputFiles = new List(); FindFilesForTar(new DirectoryInfo(BaseDir), "", InputFiles, true); CreateAndSubmitArchiveFromFiles(DepotArchivePath, Description, BaseDir, InputFiles.ToArray()); } static void CreateAndSubmitArchiveFromFiles(string DepotArchivePath, string Description, string BaseDir, IEnumerable InputFiles) { string ArchivePath = Path.Combine(CmdEnv.LocalRoot, DepotArchivePath); // Create the archive CreateTgzFromFiles(ArchivePath, BaseDir, InputFiles); // Submit the archive to P4 SubmitFile(DepotArchivePath, Description); } static void SubmitFile(string DepotPath, string Description) { if (P4Enabled) { int Changelist = P4.CreateChange(P4Env.Client, String.Format("{0} from CL#{1}", Description, P4Env.Changelist)); P4.Reconcile(Changelist, CombinePaths(PathSeparator.Slash, P4Env.ClientRoot, DepotPath)); if (!P4.TryDeleteEmptyChange(Changelist)) { if (!GlobalCommandLine.NoSubmit) { int SubmittedChangelist; P4.Submit(Changelist, out SubmittedChangelist, true, true); } } } } static void CreateTgzFromFiles(string TarPath, string BaseDir, IEnumerable InputFiles) { Console.WriteLine("Creating archive '{0}'...", TarPath); if (File.Exists(TarPath)) { CommandUtils.DeleteFile(TarPath); } using(FileStream TarFileStream = new FileStream(TarPath, FileMode.Create)) { using (GZipStream ZipStream = new GZipStream(TarFileStream, CompressionMode.Compress)) { using (BinaryWriter TarWriter = new BinaryWriter(ZipStream)) { foreach (string InputFile in InputFiles) { byte[] FileData = File.ReadAllBytes(Path.Combine(BaseDir, InputFile)); WriteFileToTar(TarWriter, InputFile, FileData); } TarWriter.Write(new byte[1024]); } } } } static void FindFilesForTar(string BaseDir, string BaseTarPath, List Files, bool bRecursive) { FindFilesForTar(new DirectoryInfo(Path.Combine(BaseDir, BaseTarPath)), BaseTarPath, Files, bRecursive); } static void FindFilesForTar(DirectoryInfo Dir, string BaseTarPath, List Files, bool bRecursive) { if (bRecursive) { foreach (DirectoryInfo ChildDir in Dir.GetDirectories()) { FindFilesForTar(ChildDir, Path.Combine(BaseTarPath, ChildDir.Name), Files, bRecursive); } } foreach (FileInfo FileInfo in Dir.GetFiles()) { Files.Add(Path.Combine(BaseTarPath, FileInfo.Name)); } } const int TarFileNameFieldLength = 99; static void WriteFileToTar(BinaryWriter Writer, string FilePath, byte[] FileData) { string NormalizedFilePath = FilePath.Replace('\\', '/'); // Generate a long header record if necessary if (NormalizedFilePath.Length > TarFileNameFieldLength) { WriteTarHeader(Writer, "././@LongLink", NormalizedFilePath.Length + 1, 'L'); byte[] FilePathBytes = new byte[NormalizedFilePath.Length + 1]; EncodeTarString(FilePathBytes, 0, FilePathBytes.Length, NormalizedFilePath); WriteTarBlocks(Writer, FilePathBytes); } // Now write the normal file WriteTarHeader(Writer, NormalizedFilePath, FileData.Length, '0'); WriteTarBlocks(Writer, FileData); } static void WriteTarHeader(BinaryWriter Writer, string FileName, int Length, char Type) { // Write the name byte[] Header = new byte[512]; // Name EncodeTarString(Header, 0, TarFileNameFieldLength, FileName); // File mode EncodeTarValue(Header, 100, 7, 0x81ff); // UID EncodeTarValue(Header, 108, 7, 0); // GID EncodeTarValue(Header, 116, 7, 0); // File size EncodeTarValue(Header, 124, 12, Length); // File timestamp EncodeTarValue(Header, 136, 12, 0); // File type ('0' = Regular file) Header[156] = (byte)Type; // Generate the the checksum (seed the checksum as if the field was initialized with 8 spaces) int Checksum = (byte)' ' * 8; for (int Idx = 0; Idx < Header.Length; Idx++) Checksum += Header[Idx]; EncodeTarValue(Header, 148, 7, Checksum); // Write the header Writer.Write(Header); } static void WriteTarBlocks(BinaryWriter Writer, byte[] Data) { Writer.Write(Data); Writer.Write(new byte[(512 - (Data.Length % 512)) % 512]); } static void EncodeTarString(byte[] Data, int Offset, int Length, string Text) { // Convert the Text to ASCII for (int Idx = 0; Idx < Math.Min(Length, Text.Length); Idx++) { if (Text[Idx] == 0 || Text[Idx] > 0x7f) { throw new AutomationException("Invalid character in TAR string"); } else { Data[Offset + Idx] = (byte)Text[Idx]; } } } static void EncodeTarValue(byte[] Data, int Offset, int Length, int Value) { // Write the value as an octal string followed by a space string ValueString = Convert.ToString(Value, 8) + " "; while (ValueString.Length < Length) ValueString = " " + ValueString; EncodeTarString(Data, Offset, Length, ValueString); } }