/// <summary>
        /// Creates a text file with the given contents.  If the contents of the text file aren't changed, it won't write the new contents to
        /// the file to avoid causing an action to be considered outdated.
        /// </summary>
        /// <param name="Location">Path to the intermediate file to create</param>
        /// <param name="Contents">Contents of the new file</param>
        /// <returns>File item for the newly created file</returns>
        public static FileItem CreateIntermediateTextFile(FileReference Location, string Contents)
        {
            // Only write the file if its contents have changed.
            if (!FileReference.Exists(Location))
            {
                DirectoryReference.CreateDirectory(Location.Directory);
                FileReference.WriteAllText(Location, Contents, GetEncodingForString(Contents));
            }
            else
            {
                string CurrentContents = Utils.ReadAllText(Location.FullName);
                if (!String.Equals(CurrentContents, Contents, StringComparison.InvariantCultureIgnoreCase))
                {
                    FileReference BackupFile = new FileReference(Location.FullName + ".old");
                    try
                    {
                        Log.TraceLog("Updating {0}: contents have changed. Saving previous version to {1}.", Location, BackupFile);
                        FileReference.Delete(BackupFile);
                        FileReference.Move(Location, BackupFile);
                    }
                    catch (Exception Ex)
                    {
                        Log.TraceWarning("Unable to rename {0} to {1}", Location, BackupFile);
                        Log.TraceLog("{0}", ExceptionUtils.FormatExceptionDetails(Ex));
                    }
                    FileReference.WriteAllText(Location, Contents, GetEncodingForString(Contents));
                }
            }

            // Reset the file info, in case it already knows about the old file
            FileItem Item = GetItemByFileReference(Location);

            Item.ResetCachedInfo();
            return(Item);
        }
Exemple #2
0
        /// <summary>
        /// Writes a schema to the given location. Avoids writing it if the file is identical.
        /// </summary>
        /// <param name="Schema">The schema to be written</param>
        /// <param name="Location">Location to write to</param>
        static void WriteSchema(XmlSchema Schema, FileReference Location)
        {
            XmlWriterSettings Settings = new XmlWriterSettings();

            Settings.Indent             = true;
            Settings.IndentChars        = "\t";
            Settings.NewLineChars       = Environment.NewLine;
            Settings.OmitXmlDeclaration = true;

            if (CachedSchemaSerializer == null)
            {
                CachedSchemaSerializer = XmlSerializer.FromTypes(new Type[] { typeof(XmlSchema) })[0];
            }

            StringBuilder Output = new StringBuilder();

            Output.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            using (XmlWriter Writer = XmlWriter.Create(Output, Settings))
            {
                XmlSerializerNamespaces Namespaces = new XmlSerializerNamespaces();
                Namespaces.Add("", "http://www.w3.org/2001/XMLSchema");
                CachedSchemaSerializer.Serialize(Writer, Schema, Namespaces);
            }

            string OutputText = Output.ToString();

            if (!FileReference.Exists(Location) || File.ReadAllText(Location.FullName) != OutputText)
            {
                DirectoryReference.CreateDirectory(Location.Directory);
                File.WriteAllText(Location.FullName, OutputText);
            }
        }
        /// <summary>
        /// Writes data for this dependency cache to disk
        /// </summary>
        private void Write()
        {
            DirectoryReference.CreateDirectory(Location.Directory);
            using (FileStream Stream = File.Open(Location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                using (BinaryArchiveWriter Writer = new BinaryArchiveWriter(Stream))
                {
                    Writer.WriteInt(CurrentVersion);

                    Writer.WriteInt(FileToIncludeInfo.Count);
                    foreach (KeyValuePair <FileItem, IncludeInfo> Pair in FileToIncludeInfo)
                    {
                        Writer.WriteCompactFileItem(Pair.Key);
                        Writer.WriteLong(Pair.Value.LastWriteTimeUtc);
                        Writer.WriteString(Pair.Value.IncludeText);
                    }

                    Writer.WriteInt(FileToReflectionInfo.Count);
                    foreach (KeyValuePair <FileItem, ReflectionInfo> Pair in FileToReflectionInfo)
                    {
                        Writer.WriteCompactFileItem(Pair.Key);
                        Writer.WriteLong(Pair.Value.LastWriteTimeUtc);
                        Writer.WriteBool(Pair.Value.bContainsMarkup);
                    }
                }
            }
            bModified = false;
        }
Exemple #4
0
        /// <summary>
        /// Exports an action graph to a JSON file
        /// </summary>
        /// <param name="Actions">The actions to write</param>
        /// <param name="OutputFile">Output file to write the actions to</param>
        public static void ExportJson(List <Action> Actions, FileReference OutputFile)
        {
            DirectoryReference.CreateDirectory(OutputFile.Directory);
            using (JsonWriter Writer = new JsonWriter(OutputFile))
            {
                Writer.WriteObjectStart();

                Writer.WriteObjectStart("Environment");
                foreach (System.Collections.DictionaryEntry Pair in Environment.GetEnvironmentVariables())
                {
                    if (!UnrealBuildTool.InitialEnvironment.Contains(Pair.Key) || (string)(UnrealBuildTool.InitialEnvironment[Pair.Key]) != (string)(Pair.Value))
                    {
                        Writer.WriteValue((string)Pair.Key, (string)Pair.Value);
                    }
                }
                Writer.WriteObjectEnd();

                Writer.WriteArrayStart("Actions");
                foreach (Action Action in Actions)
                {
                    Writer.WriteObjectStart();
                    Action.ExportJson(Writer);
                    Writer.WriteObjectEnd();
                }
                Writer.WriteArrayEnd();
                Writer.WriteObjectEnd();
            }
        }
 private static void SerializeTarget(FileReference OutputFile, UEBuildTarget BuildTarget)
 {
     DirectoryReference.CreateDirectory(OutputFile.Directory);
     using (JsonWriter Writer = new JsonWriter(OutputFile))
     {
         ExportTarget(BuildTarget, Writer);
     }
 }
 /// <summary>
 /// Write the receipt to disk.
 /// </summary>
 /// <param name="FileName">The file to write to</param>
 public void Write(FileReference FileName)
 {
     DirectoryReference.CreateDirectory(FileName.Directory);
     using (StreamWriter Writer = new StreamWriter(FileName.FullName))
     {
         Write(Writer);
     }
 }
 /// <summary>
 /// Saves a makefile to disk
 /// </summary>
 /// <param name="Location">Path to save the makefile to</param>
 public void Save(FileReference Location)
 {
     DirectoryReference.CreateDirectory(Location.Directory);
     using (BinaryArchiveWriter Writer = new BinaryArchiveWriter(Location))
     {
         Writer.WriteInt(CurrentVersion);
         Write(Writer);
     }
 }
Exemple #8
0
 /// <summary>
 /// Saves this action history to disk
 /// </summary>
 void Save()
 {
     DirectoryReference.CreateDirectory(Location.Directory);
     using (BinaryArchiveWriter Writer = new BinaryArchiveWriter(Location))
     {
         Writer.WriteInt(CurrentVersion);
         Writer.WriteDictionary(OutputItemToCommandLineHash, Key => Writer.WriteFileItem(Key), Value => Writer.WriteFixedSizeByteArray(Value));
     }
     bModified = false;
 }
Exemple #9
0
 /// <summary>
 /// Create a default config file at the given location
 /// </summary>
 /// <param name="Location">Location to read from</param>
 static void CreateDefaultConfigFile(FileReference Location)
 {
     DirectoryReference.CreateDirectory(Location.Directory);
     using (StreamWriter Writer = new StreamWriter(Location.FullName))
     {
         Writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
         Writer.WriteLine("<Configuration xmlns=\"{0}\">", XmlConfigFile.SchemaNamespaceURI);
         Writer.WriteLine("</Configuration>");
     }
 }
Exemple #10
0
        /// <summary>
        /// Saves the dependency cache to disk using the update time as the creation time.
        /// </summary>
        public void Save()
        {
            // Only save if we've made changes to it since load.
            if (bIsDirty)
            {
                DateTime TimerStartTime = DateTime.UtcNow;

                // Save update date as new creation date.
                CreationTimeUtc = UpdateTimeUtc;

                // Serialize the cache to disk.
                try
                {
                    DirectoryReference.CreateDirectory(BackingFile.Directory);
                    using (BinaryWriter Writer = new BinaryWriter(new FileStream(BackingFile.FullName, FileMode.Create, FileAccess.Write)))
                    {
                        Writer.Write(FileSignature);
                        Serialize(Writer);
                    }
                }
                catch (Exception Ex)
                {
                    Log.TraceError("Failed to write dependency cache: {0}", Ex.Message);
                }

                if (UnrealBuildTool.bPrintPerformanceInfo)
                {
                    TimeSpan TimerDuration = DateTime.UtcNow - TimerStartTime;
                    Log.TraceInformation("Saving IncludeFileCache took " + TimerDuration.TotalSeconds + "s");
                }
            }
            else
            {
                if (UnrealBuildTool.bPrintPerformanceInfo)
                {
                    Log.TraceInformation("IncludeFileCache did not need to be saved (bIsDirty=false)");
                }
            }

            FileReference MutexPath = BackingFile + ".buildmutex";

            if (FileReference.Exists(MutexPath))
            {
                try
                {
                    FileReference.Delete(MutexPath);
                }
                catch
                {
                    // We don't care if we couldn't delete this file, as maybe it couldn't have been created in the first place.
                }
            }
        }
Exemple #11
0
        protected override bool WriteMasterProjectFile(ProjectFile UBTProject)
        {
            VSCodeDir = DirectoryReference.Combine(MasterProjectPath, ".vscode");
            DirectoryReference.CreateDirectory(VSCodeDir);

            HostPlatform = BuildHostPlatform.Current.Platform;

            WriteCppPropertiesFile();
            WriteTasksFile();
            WriteLaunchFile();

            return(true);
        }
        /// <summary>
        /// Generates documentation files for the available settings, by merging the XML documentation from the compiler.
        /// </summary>
        /// <param name="OutputFile">The documentation file to write</param>
        public static void WriteDocumentation(FileReference OutputFile)
        {
            // Find all the configurable types
            List <Type> ConfigTypes = FindConfigurableTypes();

            // Find all the configurable fields from the given types
            Dictionary <string, Dictionary <string, FieldInfo> > CategoryToFields = new Dictionary <string, Dictionary <string, FieldInfo> >();

            FindConfigurableFields(ConfigTypes, CategoryToFields);
            CategoryToFields = CategoryToFields.Where(x => x.Value.Count > 0).ToDictionary(x => x.Key, x => x.Value);

            // Get the path to the XML documentation
            FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml");

            if (!FileReference.Exists(InputDocumentationFile))
            {
                throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile);
            }

            // Read the documentation
            XmlDocument InputDocumentation = new XmlDocument();

            InputDocumentation.Load(InputDocumentationFile.FullName);

            // Make sure we can write to the output file
            if (FileReference.Exists(OutputFile))
            {
                FileReference.MakeWriteable(OutputFile);
            }
            else
            {
                DirectoryReference.CreateDirectory(OutputFile.Directory);
            }

            // Generate the documentation file
            if (OutputFile.HasExtension(".udn"))
            {
                WriteDocumentationUDN(OutputFile, InputDocumentation, CategoryToFields);
            }
            else if (OutputFile.HasExtension(".html"))
            {
                WriteDocumentationHTML(OutputFile, InputDocumentation, CategoryToFields);
            }
            else
            {
                throw new BuildException("Unable to detect format from extension of output file ({0})", OutputFile);
            }

            // Success!
            Log.TraceInformation("Written documentation to {0}.", OutputFile);
        }
Exemple #13
0
        /// <summary>
        /// Writes information about the targets in an assembly to a file
        /// </summary>
        /// <param name="ProjectFile">The project file for the targets being built</param>
        /// <param name="Assembly">The rules assembly for this target</param>
        /// <param name="OutputFile">Output file to write to</param>
        /// <param name="Arguments"></param>
        public static void WriteTargetInfo(FileReference ProjectFile, RulesAssembly Assembly, FileReference OutputFile, CommandLineArguments Arguments)
        {
            // Construct all the targets in this assembly
            List <string> TargetNames = new List <string>();

            Assembly.GetAllTargetNames(TargetNames, false);

            // Write the output file
            DirectoryReference.CreateDirectory(OutputFile.Directory);
            using (JsonWriter Writer = new JsonWriter(OutputFile))
            {
                Writer.WriteObjectStart();
                Writer.WriteArrayStart("Targets");
                foreach (string TargetName in TargetNames)
                {
                    // skip target rules that are platform extension or platform group specializations
                    string[] TargetPathSplit = TargetName.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
                    if (TargetPathSplit.Length > 1 && (UnrealTargetPlatform.IsValidName(TargetPathSplit.Last()) || UnrealPlatformGroup.IsValidName(TargetPathSplit.Last())))
                    {
                        continue;
                    }

                    // Construct the rules object
                    TargetRules TargetRules;
                    try
                    {
                        string Architecture = UEBuildPlatform.GetBuildPlatform(BuildHostPlatform.Current.Platform).GetDefaultArchitecture(ProjectFile);
                        TargetRules = Assembly.CreateTargetRules(TargetName, BuildHostPlatform.Current.Platform, UnrealTargetConfiguration.Development, Architecture, ProjectFile, Arguments);
                    }
                    catch (Exception Ex)
                    {
                        Log.TraceWarning("Unable to construct target rules for {0}", TargetName);
                        Log.TraceVerbose(ExceptionUtils.FormatException(Ex));
                        continue;
                    }

                    // Write the target info
                    Writer.WriteObjectStart();
                    Writer.WriteValue("Name", TargetName);
                    Writer.WriteValue("Path", Assembly.GetTargetFileName(TargetName).ToString());
                    Writer.WriteValue("Type", TargetRules.Type.ToString());
                    Writer.WriteObjectEnd();
                }
                Writer.WriteArrayEnd();
                Writer.WriteObjectEnd();
            }
        }
Exemple #14
0
        /// <summary>
        /// Creates directories for all the items produced by actions in the provided outdated action
        /// dictionary.
        /// </summary>
        public static void CreateDirectoriesForProducedItems(List <Action> OutdatedActions)
        {
            HashSet <DirectoryReference> OutputDirectories = new HashSet <DirectoryReference>();

            foreach (Action OutdatedAction in OutdatedActions)
            {
                foreach (FileItem ProducedItem in OutdatedAction.ProducedItems)
                {
                    OutputDirectories.Add(ProducedItem.Location.Directory);
                }
            }
            foreach (DirectoryReference OutputDirectory in OutputDirectories)
            {
                if (!DirectoryReference.Exists(OutputDirectory))
                {
                    DirectoryReference.CreateDirectory(OutputDirectory);
                }
            }
        }
        /// <summary>
        /// Writes data for this dependency cache to disk
        /// </summary>
        private void Write()
        {
            DirectoryReference.CreateDirectory(Location.Directory);
            using (FileStream Stream = File.Open(Location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                using (BinaryArchiveWriter Writer = new BinaryArchiveWriter(Stream))
                {
                    Writer.WriteInt(CurrentVersion);

                    Writer.WriteInt(DependencyFileToInfo.Count);
                    foreach (KeyValuePair <FileItem, DependencyInfo> Pair in DependencyFileToInfo)
                    {
                        Writer.WriteFileItem(Pair.Key);
                        Pair.Value.Write(Writer);
                    }
                }
            }
            bModified = false;
        }
Exemple #16
0
 /// <summary>
 /// Write the deployment info to a file on disk
 /// </summary>
 /// <param name="Location">File to write to</param>
 public void Write(FileReference Location)
 {
     DirectoryReference.CreateDirectory(Location.Directory);
     using (BinaryWriter Writer = new BinaryWriter(File.Open(Location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read)))
     {
         Writer.Write(ProjectFile);
         Writer.Write(TargetFile);
         Writer.Write(AppName);
         Writer.Write(TargetName);
         Writer.Write((Int32)TargetType);
         Writer.Write((Int32)Platform);
         Writer.Write((Int32)Configuration);
         Writer.Write(OutputPaths, (x, i) => x.Write(i));
         Writer.Write(EngineIntermediateDirectory);
         Writer.Write(ProjectDirectory);
         Writer.Write(BuildReceiptFileName);
         Writer.Write(bCreateStubIPA);
         Writer.Write(AndroidArchitectures, (w, e) => w.Write(e));
         Writer.Write(AndroidGPUArchitectures, (w, e) => w.Write(e));
     }
 }
Exemple #17
0
        /// <summary>
        /// Writes information about the targets in an assembly to a file
        /// </summary>
        /// <param name="ProjectFile">The project file for the targets being built</param>
        /// <param name="Assembly">The rules assembly for this target</param>
        /// <param name="OutputFile">Output file to write to</param>
        /// <param name="Arguments"></param>
        public static void WriteTargetInfo(FileReference ProjectFile, RulesAssembly Assembly, FileReference OutputFile, CommandLineArguments Arguments)
        {
            // Construct all the targets in this assembly
            List <string> TargetNames = new List <string>();

            Assembly.GetAllTargetNames(TargetNames, false);

            // Write the output file
            DirectoryReference.CreateDirectory(OutputFile.Directory);
            using (JsonWriter Writer = new JsonWriter(OutputFile))
            {
                Writer.WriteObjectStart();
                Writer.WriteArrayStart("Targets");
                foreach (string TargetName in TargetNames)
                {
                    // Construct the rules object
                    TargetRules TargetRules;
                    try
                    {
                        string Architecture = UEBuildPlatform.GetBuildPlatform(BuildHostPlatform.Current.Platform).GetDefaultArchitecture(ProjectFile);
                        TargetRules = Assembly.CreateTargetRules(TargetName, BuildHostPlatform.Current.Platform, UnrealTargetConfiguration.Development, Architecture, ProjectFile, Arguments);
                    }
                    catch (Exception Ex)
                    {
                        Log.TraceWarning("Unable to construct target rules for {0}", TargetName);
                        Log.TraceVerbose(ExceptionUtils.FormatException(Ex));
                        continue;
                    }

                    // Write the target info
                    Writer.WriteObjectStart();
                    Writer.WriteValue("Name", TargetName);
                    Writer.WriteValue("Path", Assembly.GetTargetFileName(TargetName).ToString());
                    Writer.WriteValue("Type", TargetRules.Type.ToString());
                    Writer.WriteObjectEnd();
                }
                Writer.WriteArrayEnd();
                Writer.WriteObjectEnd();
            }
        }
Exemple #18
0
        /// <summary>
        /// Download multiple files from the remote Mac
        /// </summary>
        /// <param name="Files">List of local files to download</param>
        void DownloadFiles(IEnumerable <FileReference> Files)
        {
            List <FileReference>[] FileGroups = new List <FileReference> [Mappings.Count];
            for (int Idx = 0; Idx < Mappings.Count; Idx++)
            {
                FileGroups[Idx] = new List <FileReference>();
            }
            foreach (FileReference File in Files)
            {
                int MappingIdx = Mappings.FindIndex(x => File.IsUnderDirectory(x.LocalDirectory));
                if (MappingIdx == -1)
                {
                    throw new BuildException("File for download '{0}' is not under the engine or project directory.", File);
                }
                FileGroups[MappingIdx].Add(File);
            }
            for (int Idx = 0; Idx < Mappings.Count; Idx++)
            {
                if (FileGroups[Idx].Count > 0)
                {
                    FileReference DownloadListLocation = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Rsync", "Download.txt");
                    DirectoryReference.CreateDirectory(DownloadListLocation.Directory);
                    FileReference.WriteAllLines(DownloadListLocation, FileGroups[Idx].Select(x => x.MakeRelativeTo(Mappings[Idx].LocalDirectory).Replace('\\', '/')));

                    List <string> Arguments = new List <string>(CommonRsyncArguments);
                    Arguments.Add(String.Format("--files-from=\"{0}\"", GetLocalCygwinPath(DownloadListLocation)));
                    Arguments.Add(String.Format("\"{0}@{1}\":'{2}/'", UserName, ServerName, Mappings[Idx].RemoteDirectory));
                    Arguments.Add(String.Format("\"{0}/\"", GetLocalCygwinPath(Mappings[Idx].LocalDirectory)));

                    int Result = Rsync(String.Join(" ", Arguments));
                    if (Result != 0)
                    {
                        throw new BuildException("Unable to download files from remote Mac (exit code {0})", Result);
                    }
                }
            }
        }
        /// <summary>
        /// Writes the coalesced config hierarchy to disk
        /// </summary>
        /// <param name="Location">File to write to</param>
        public void Write(FileReference Location)
        {
            DirectoryReference.CreateDirectory(Location.Directory);
            using (BinaryWriter Writer = new BinaryWriter(File.Open(Location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read)))
            {
                Writer.Write(SerializationVersion);

                // Save all the input files. The cache will not be valid if these change.
                Writer.Write(InputFiles, Item => Writer.Write(Item));

                // Write all the categories
                Writer.Write(TypeToValues.Count);
                foreach (KeyValuePair <Type, KeyValuePair <FieldInfo, object>[]> TypePair in TypeToValues)
                {
                    Writer.Write(TypePair.Key.Name);
                    Writer.Write(TypePair.Value.Length);
                    foreach (KeyValuePair <FieldInfo, object> FieldPair in TypePair.Value)
                    {
                        Writer.Write(FieldPair.Key.Name);
                        Writer.Write(FieldPair.Key.FieldType, FieldPair.Value);
                    }
                }
            }
        }
 /// <summary>
 ///
 /// </summary>
 public void Save(FileReference InFile)
 {
     DirectoryReference.CreateDirectory(InFile.Directory);
     FileReference.WriteAllText(InFile, fastJSON.JSON.Instance.ToJSON(this, new fastJSON.JSONParameters {
     }));
 }
Exemple #21
0
        public override bool ExecuteActions(List <Action> Actions, bool bLogDetailedActionStats)
        {
            if (Actions.Count == 0)
            {
                return(true);
            }

            // Clean the intermediate directory in case there are any leftovers from previous builds
            if (DirectoryReference.Exists(IntermediateDir))
            {
                DirectoryReference.Delete(IntermediateDir, true);
            }

            DirectoryReference.CreateDirectory(IntermediateDir);
            if (!DirectoryReference.Exists(IntermediateDir))
            {
                throw new BuildException($"Failed to create directory \"{IntermediateDir}\".");
            }

            // Build the json script file to describe all the actions and their dependencies
            var ActionIds = Actions.ToDictionary(a => a, a => Guid.NewGuid().ToString());

            File.WriteAllText(ScriptFile.FullName, Json.Serialize(new Dictionary <string, object>()
            {
                ["jobs"] = Actions.ToDictionary(a => ActionIds[a], a =>
                {
                    var Job = new Dictionary <string, object>()
                    {
                        ["title"]             = a.StatusDescription,
                        ["command"]           = $"\"{a.CommandPath}\" {a.CommandArguments}",
                        ["working_directory"] = a.WorkingDirectory.FullName,
                        ["dependencies"]      = a.PrerequisiteActions.Select(p => ActionIds[p]).ToArray(),
                        ["run_locally"]       = !(a.bCanExecuteRemotely && a.bCanExecuteRemotelyWithSNDBS)
                    };

                    if (a.PrerequisiteItems.Count > 0)
                    {
                        Job["explicit_input_files"] = a.PrerequisiteItems.Select(i => new Dictionary <string, object>()
                        {
                            ["filename"] = i.AbsolutePath
                        }).ToList();
                    }

                    if (EnableEcho)
                    {
                        var EchoString = string.Join(" ", string.IsNullOrWhiteSpace(a.CommandDescription) ? string.Empty : $"[{a.CommandDescription}]", a.StatusDescription);
                        if (!string.IsNullOrWhiteSpace(EchoString))
                        {
                            Job["echo"] = EchoString;
                        }
                    }

                    return(Job);
                })
            }));

            PrepareToolTemplates();
            bool bHasRewrites = GenerateSNDBSIncludeRewriteRules();

            var LocalProcess = new Process();

            LocalProcess.StartInfo           = new ProcessStartInfo(SNDBSExecutable, $"-q -p \"Unreal Engine Tasks\" -s \"{ScriptFile}\" -templates \"{IntermediateDir}\"{(bHasRewrites ? $" --include-rewrite-rules \"{IncludeRewriteRulesFile}\"" : "")}");
            LocalProcess.OutputDataReceived += (Sender, Args) => Log.TraceInformation("{0}", Args.Data);
            LocalProcess.ErrorDataReceived  += (Sender, Args) => Log.TraceInformation("{0}", Args.Data);
            return(Utils.RunLocalProcess(LocalProcess) == 0);
        }
Exemple #22
0
        public static void WriteDocumentation(Type RulesType, FileReference OutputFile)
        {
            // Get the path to the XML documentation
            FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml");

            if (!FileReference.Exists(InputDocumentationFile))
            {
                throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile);
            }

            // Read the documentation
            XmlDocument InputDocumentation = new XmlDocument();

            InputDocumentation.Load(InputDocumentationFile.FullName);

            // Filter the properties into read-only and read/write lists
            List <FieldInfo> ReadOnlyFields  = new List <FieldInfo>();
            List <FieldInfo> ReadWriteFields = new List <FieldInfo>();

            foreach (FieldInfo Field in RulesType.GetFields(BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.Public))
            {
                if (!Field.FieldType.IsClass || !Field.FieldType.Name.EndsWith("TargetRules"))
                {
                    if (Field.IsInitOnly)
                    {
                        ReadOnlyFields.Add(Field);
                    }
                    else
                    {
                        ReadWriteFields.Add(Field);
                    }
                }
            }

            // Make sure the output file is writable
            if (FileReference.Exists(OutputFile))
            {
                FileReference.MakeWriteable(OutputFile);
            }
            else
            {
                DirectoryReference.CreateDirectory(OutputFile.Directory);
            }

            // Generate the documentation file
            if (OutputFile.HasExtension(".udn"))
            {
                WriteDocumentationUDN(OutputFile, ReadOnlyFields, ReadWriteFields, InputDocumentation);
            }
            else if (OutputFile.HasExtension(".html"))
            {
                WriteDocumentationHTML(OutputFile, ReadOnlyFields, ReadWriteFields, InputDocumentation);
            }
            else
            {
                throw new BuildException("Unable to detect format from extension of output file ({0})", OutputFile);
            }

            // Success!
            Log.TraceInformation("Written documentation to {0}.", OutputFile);
        }
Exemple #23
0
        /// <summary>
        /// Generates documentation files for the available settings, by merging the XML documentation from the compiler.
        /// </summary>
        /// <param name="OutputFile">The documentation file to write</param>
        public static void WriteDocumentation(FileReference OutputFile)
        {
            // Find all the configurable types
            List <Type> ConfigTypes = FindConfigurableTypes();

            // Find all the configurable fields from the given types
            Dictionary <string, Dictionary <string, FieldInfo> > CategoryToFields = new Dictionary <string, Dictionary <string, FieldInfo> >();

            FindConfigurableFields(ConfigTypes, CategoryToFields);

            // Get the path to the XML documentation
            FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml");

            if (!FileReference.Exists(InputDocumentationFile))
            {
                throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile);
            }

            // Read the documentation
            XmlDocument InputDocumentation = new XmlDocument();

            InputDocumentation.Load(InputDocumentationFile.FullName);

            // Make sure we can write to the output file
            if (FileReference.Exists(OutputFile))
            {
                FileReference.MakeWriteable(OutputFile);
            }
            else
            {
                DirectoryReference.CreateDirectory(OutputFile.Directory);
            }

            // Generate the documentation file
            using (StreamWriter Writer = new StreamWriter(OutputFile.FullName))
            {
                Writer.WriteLine("<html>");
                Writer.WriteLine("  <body>");
                Writer.WriteLine("  <h2>BuildConfiguration Properties</h2>");
                foreach (KeyValuePair <string, Dictionary <string, FieldInfo> > CategoryPair in CategoryToFields)
                {
                    string CategoryName = CategoryPair.Key;
                    Writer.WriteLine("    <h3>{0}</h3>", CategoryName);
                    Writer.WriteLine("    <dl>");

                    Dictionary <string, FieldInfo> Fields = CategoryPair.Value;
                    foreach (KeyValuePair <string, FieldInfo> FieldPair in Fields)
                    {
                        string FieldName = FieldPair.Key;

                        FieldInfo Field = FieldPair.Value;
                        XmlNode   Node  = InputDocumentation.SelectSingleNode(String.Format("//member[@name='F:{0}.{1}']/summary", Field.DeclaringType.FullName, Field.Name));
                        if (Node != null)
                        {
                            // Reflow the comments into paragraphs, assuming that each paragraph will be separated by a blank line
                            List <string> Lines = new List <string>(Node.InnerText.Trim().Split('\n').Select(x => x.Trim()));
                            for (int Idx = Lines.Count - 1; Idx > 0; Idx--)
                            {
                                if (Lines[Idx - 1].Length > 0 && !Lines[Idx].StartsWith("*") && !Lines[Idx].StartsWith("-"))
                                {
                                    Lines[Idx - 1] += " " + Lines[Idx];
                                    Lines.RemoveAt(Idx);
                                }
                            }

                            // Write the result to the .udn file
                            if (Lines.Count > 0)
                            {
                                Writer.WriteLine("      <dt>{0}</dt>", FieldName);

                                if (Lines.Count == 1)
                                {
                                    Writer.WriteLine("      <dd>{0}</dd>", Lines[0]);
                                }
                                else
                                {
                                    Writer.WriteLine("      <dd>");
                                    for (int Idx = 0; Idx < Lines.Count; Idx++)
                                    {
                                        if (Lines[Idx].StartsWith("*") || Lines[Idx].StartsWith("-"))
                                        {
                                            Writer.WriteLine("        <ul>");
                                            for (; Idx < Lines.Count && (Lines[Idx].StartsWith("*") || Lines[Idx].StartsWith("-")); Idx++)
                                            {
                                                Writer.WriteLine("          <li>{0}</li>", Lines[Idx].Substring(1).TrimStart());
                                            }
                                            Writer.WriteLine("        </ul>");
                                        }
                                        else
                                        {
                                            Writer.WriteLine("        {0}", Lines[Idx]);
                                        }
                                    }
                                    Writer.WriteLine("      </dd>");
                                }
                            }
                        }
                    }

                    Writer.WriteLine("    </dl>");
                }
                Writer.WriteLine("  </body>");
                Writer.WriteLine("</html>");
            }

            // Success!
            Log.TraceInformation("Written documentation to {0}.", OutputFile);
        }
        public static void WriteDocumentation(Type RulesType, FileReference OutputFile)
        {
            // Get the path to the XML documentation
            FileReference InputDocumentationFile = new FileReference(Assembly.GetExecutingAssembly().Location).ChangeExtension(".xml");

            if (!FileReference.Exists(InputDocumentationFile))
            {
                throw new BuildException("Generated assembly documentation not found at {0}.", InputDocumentationFile);
            }

            // Read the documentation
            XmlDocument InputDocumentation = new XmlDocument();

            InputDocumentation.Load(InputDocumentationFile.FullName);

            // Filter the properties into read-only and read/write lists
            List <FieldInfo> ReadOnlyFields  = new List <FieldInfo>();
            List <FieldInfo> ReadWriteFields = new List <FieldInfo>();

            foreach (FieldInfo Field in RulesType.GetFields(BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.Public))
            {
                if (!Field.FieldType.IsClass || !Field.FieldType.Name.EndsWith("TargetRules"))
                {
                    if (Field.IsInitOnly)
                    {
                        ReadOnlyFields.Add(Field);
                    }
                    else
                    {
                        ReadWriteFields.Add(Field);
                    }
                }
            }

            // Make sure the output file is writable
            if (FileReference.Exists(OutputFile))
            {
                FileReference.MakeWriteable(OutputFile);
            }
            else
            {
                DirectoryReference.CreateDirectory(OutputFile.Directory);
            }

            // Generate the documentation file
            using (StreamWriter Writer = new StreamWriter(OutputFile.FullName))
            {
                Writer.WriteLine("<html>");
                Writer.WriteLine("  <body>");
                if (ReadOnlyFields.Count > 0)
                {
                    Writer.WriteLine("    <h2>Read-Only Properties</h2>");
                    Writer.WriteLine("    <dl>");
                    foreach (FieldInfo Field in ReadOnlyFields)
                    {
                        OutputField(InputDocumentation, Field, Writer);
                    }
                    Writer.WriteLine("    </dl>");
                }
                if (ReadWriteFields.Count > 0)
                {
                    Writer.WriteLine("    <h2>Read/Write Properties</h2>");
                    Writer.WriteLine("    <dl>");
                    foreach (FieldInfo Field in ReadWriteFields)
                    {
                        OutputField(InputDocumentation, Field, Writer);
                    }
                    Writer.WriteLine("    </dl>");
                }
                Writer.WriteLine("  </body>");
                Writer.WriteLine("</html>");
            }

            // Success!
            Log.TraceInformation("Written documentation to {0}.", OutputFile);
        }
Exemple #25
0
        /// <summary>
        /// Build a target remotely
        /// </summary>
        /// <param name="TargetDesc">Descriptor for the target to build</param>
        /// <param name="RemoteLogFile">Path to store the remote log file</param>
        /// <returns>True if the build succeeded, false otherwise</returns>
        public bool Build(TargetDescriptor TargetDesc, FileReference RemoteLogFile)
        {
            // Get the directory for working files
            DirectoryReference BaseDir = DirectoryReference.FromFile(TargetDesc.ProjectFile) ?? UnrealBuildTool.EngineDirectory;
            DirectoryReference TempDir = DirectoryReference.Combine(BaseDir, "Intermediate", "Remote", TargetDesc.Name, TargetDesc.Platform.ToString(), TargetDesc.Configuration.ToString());

            DirectoryReference.CreateDirectory(TempDir);

            // Compile the rules assembly
            RulesCompiler.CreateTargetRulesAssembly(TargetDesc.ProjectFile, TargetDesc.Name, false, false, TargetDesc.ForeignPlugin);

            // Path to the local manifest file. This has to be translated from the remote format after the build is complete.
            List <FileReference> LocalManifestFiles = new List <FileReference>();

            // Path to the remote manifest file
            FileReference RemoteManifestFile = FileReference.Combine(TempDir, "Manifest.xml");

            // Prepare the arguments we will pass to the remote build
            List <string> RemoteArguments = new List <string>();

            RemoteArguments.Add(TargetDesc.Name);
            RemoteArguments.Add(TargetDesc.Platform.ToString());
            RemoteArguments.Add(TargetDesc.Configuration.ToString());
            RemoteArguments.Add("-SkipRulesCompile");             // Use the rules assembly built locally
            RemoteArguments.Add("-ForceXmlConfigCache");          // Use the XML config cache built locally, since the remote won't have it
            RemoteArguments.Add(String.Format("-Log={0}", GetRemotePath(RemoteLogFile)));
            RemoteArguments.Add(String.Format("-Manifest={0}", GetRemotePath(RemoteManifestFile)));

            if (TargetDesc.ProjectFile != null)
            {
                RemoteArguments.Add(String.Format("-Project={0}", GetRemotePath(TargetDesc.ProjectFile)));
            }

            foreach (string LocalArgument in TargetDesc.AdditionalArguments)
            {
                int EqualsIdx = LocalArgument.IndexOf('=');
                if (EqualsIdx == -1)
                {
                    RemoteArguments.Add(LocalArgument);
                    continue;
                }

                string Key   = LocalArgument.Substring(0, EqualsIdx);
                string Value = LocalArgument.Substring(EqualsIdx + 1);

                if (Key.Equals("-Log", StringComparison.InvariantCultureIgnoreCase))
                {
                    // We are already writing to the local log file. The remote will produce a different log (RemoteLogFile)
                    continue;
                }
                if (Key.Equals("-Manifest", StringComparison.InvariantCultureIgnoreCase))
                {
                    LocalManifestFiles.Add(new FileReference(Value));
                    continue;
                }

                string RemoteArgument = LocalArgument;
                foreach (RemoteMapping Mapping in Mappings)
                {
                    if (Value.StartsWith(Mapping.LocalDirectory.FullName, StringComparison.InvariantCultureIgnoreCase))
                    {
                        RemoteArgument = String.Format("{0}={1}", Key, GetRemotePath(Value));
                        break;
                    }
                }
                RemoteArguments.Add(RemoteArgument);
            }

            // Handle any per-platform setup that is required
            if (TargetDesc.Platform == UnrealTargetPlatform.IOS || TargetDesc.Platform == UnrealTargetPlatform.TVOS)
            {
                // Always generate a .stub
                RemoteArguments.Add("-CreateStub");

                // Get the provisioning data for this project
                IOSProvisioningData ProvisioningData = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(TargetDesc.Platform)).ReadProvisioningData(TargetDesc.ProjectFile);
                if (ProvisioningData == null || ProvisioningData.MobileProvisionFile == null)
                {
                    throw new BuildException("Unable to find mobile provision for {0}. See log for more information.", TargetDesc.Name);
                }

                // Create a local copy of the provision
                FileReference MobileProvisionFile = FileReference.Combine(TempDir, ProvisioningData.MobileProvisionFile.GetFileName());
                if (FileReference.Exists(MobileProvisionFile))
                {
                    FileReference.SetAttributes(MobileProvisionFile, FileAttributes.Normal);
                }
                FileReference.Copy(ProvisioningData.MobileProvisionFile, MobileProvisionFile, true);
                Log.TraceInformation("[Remote] Uploading {0}", MobileProvisionFile);
                UploadFile(MobileProvisionFile);

                // Extract the certificate for the project
                FileReference CertificateFile = FileReference.Combine(TempDir, "Certificate.p12");
                if (!FileReference.Exists(CertificateFile))
                {
                    StringBuilder Arguments = new StringBuilder("ExportCertificate");
                    if (TargetDesc.ProjectFile == null)
                    {
                        Arguments.AppendFormat(" \"{0}\"", UnrealBuildTool.EngineSourceDirectory);
                    }
                    else
                    {
                        Arguments.AppendFormat(" \"{0}\"", TargetDesc.ProjectFile.Directory);
                    }
                    Arguments.AppendFormat(" -certificate \"{0}\"", CertificateFile);

                    ProcessStartInfo StartInfo = new ProcessStartInfo();
                    StartInfo.FileName  = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Binaries", "DotNET", "IOS", "IPhonePackager.exe").FullName;
                    StartInfo.Arguments = Arguments.ToString();
                    if (Utils.RunLocalProcessAndLogOutput(StartInfo) != 0)
                    {
                        throw new BuildException("IphonePackager failed.");
                    }
                }

                // Upload the certificate to the remote
                Log.TraceInformation("[Remote] Uploading {0}", CertificateFile);
                UploadFile(CertificateFile);

                // Tell the remote UBT instance to use them
                RemoteArguments.Add(String.Format("-ImportProvision={0}", GetRemotePath(MobileProvisionFile)));
                RemoteArguments.Add(String.Format("-ImportCertificate={0}", GetRemotePath(CertificateFile)));
                RemoteArguments.Add(String.Format("-ImportCertificatePassword=A"));
            }

            // Upload the workspace files
            Log.TraceInformation("[Remote] Uploading workspace files");
            UploadWorkspace(TempDir);

            // Fixup permissions on any shell scripts
            Execute(RemoteBaseDir, String.Format("chmod +x {0}/Build/BatchFiles/Mac/*.sh", EscapeShellArgument(GetRemotePath(UnrealBuildTool.EngineDirectory))));

            // Execute the compile
            Log.TraceInformation("[Remote] Executing build");

            StringBuilder BuildCommandLine = new StringBuilder("Engine/Build/BatchFiles/Mac/Build.sh");

            foreach (string RemoteArgument in RemoteArguments)
            {
                BuildCommandLine.AppendFormat(" {0}", EscapeShellArgument(RemoteArgument));
            }

            int Result = Execute(GetRemotePath(UnrealBuildTool.RootDirectory), BuildCommandLine.ToString());

            if (Result != 0)
            {
                if (RemoteLogFile != null)
                {
                    Log.TraceInformation("[Remote] Downloading {0}", RemoteLogFile);
                    DownloadFile(RemoteLogFile);
                }
                return(false);
            }

            // Download the manifest
            Log.TraceInformation("[Remote] Downloading {0}", RemoteManifestFile);
            DownloadFile(RemoteManifestFile);

            // Convert the manifest to local form
            BuildManifest Manifest = Utils.ReadClass <BuildManifest>(RemoteManifestFile.FullName);

            for (int Idx = 0; Idx < Manifest.BuildProducts.Count; Idx++)
            {
                Manifest.BuildProducts[Idx] = GetLocalPath(Manifest.BuildProducts[Idx]).FullName;
            }

            // Download the files from the remote
            if (TargetDesc.AdditionalArguments.Any(x => x.Equals("-GenerateManifest", StringComparison.InvariantCultureIgnoreCase)))
            {
                LocalManifestFiles.Add(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "Manifest.xml"));
            }
            else
            {
                Log.TraceInformation("[Remote] Downloading build products");

                List <FileReference> FilesToDownload = new List <FileReference>();
                FilesToDownload.Add(RemoteLogFile);
                FilesToDownload.AddRange(Manifest.BuildProducts.Select(x => new FileReference(x)));
                DownloadFiles(FilesToDownload);
            }

            // Write out all the local manifests
            foreach (FileReference LocalManifestFile in LocalManifestFiles)
            {
                Log.TraceInformation("[Remote] Writing {0}", LocalManifestFile);
                Utils.WriteClass <BuildManifest>(Manifest, LocalManifestFile.FullName, "");
            }
            return(true);
        }
Exemple #26
0
        /// <summary>
        /// Initialize the config system with the given types
        /// </summary>
        /// <param name="OverrideCacheFile">Force use of the cached XML config without checking if it's valid (useful for remote builds)</param>
        public static void ReadConfigFiles(FileReference OverrideCacheFile)
        {
            // Find all the configurable types
            List <Type> ConfigTypes = FindConfigurableTypes();

            // Update the cache if necessary
            if (OverrideCacheFile != null)
            {
                // Set the cache file to the overriden value
                CacheFile = OverrideCacheFile;

                // Never rebuild the cache; just try to load it.
                if (!XmlConfigData.TryRead(CacheFile, ConfigTypes, out Values))
                {
                    throw new BuildException("Unable to load XML config cache ({0})", CacheFile);
                }
            }
            else
            {
                // Get the default cache file
                CacheFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "XmlConfigCache.bin");
                if (UnrealBuildTool.IsEngineInstalled())
                {
                    DirectoryReference UserSettingsDir = Utils.GetUserSettingDirectory();
                    if (UserSettingsDir != null)
                    {
                        CacheFile = FileReference.Combine(UserSettingsDir, "UnrealEngine", String.Format("XmlConfigCache-{0}.bin", UnrealBuildTool.RootDirectory.FullName.Replace(":", "").Replace(Path.DirectorySeparatorChar, '+')));
                    }
                }

                // Find all the input files
                FileReference[] InputFiles = FindInputFiles().Select(x => x.Location).ToArray();

                // Get the path to the schema
                FileReference SchemaFile = GetSchemaLocation();

                // Try to read the existing cache from disk
                XmlConfigData CachedValues;
                if (IsCacheUpToDate(CacheFile, InputFiles) && FileReference.Exists(SchemaFile))
                {
                    if (XmlConfigData.TryRead(CacheFile, ConfigTypes, out CachedValues) && Enumerable.SequenceEqual(InputFiles, CachedValues.InputFiles))
                    {
                        Values = CachedValues;
                    }
                }

                // If that failed, regenerate it
                if (Values == null)
                {
                    // Find all the configurable fields from the given types
                    Dictionary <string, Dictionary <string, FieldInfo> > CategoryToFields = new Dictionary <string, Dictionary <string, FieldInfo> >();
                    FindConfigurableFields(ConfigTypes, CategoryToFields);

                    // Create a schema for the config files
                    XmlSchema Schema = CreateSchema(CategoryToFields);
                    if (!UnrealBuildTool.IsEngineInstalled())
                    {
                        WriteSchema(Schema, SchemaFile);
                    }

                    // Read all the XML files and validate them against the schema
                    Dictionary <Type, Dictionary <FieldInfo, object> > TypeToValues = new Dictionary <Type, Dictionary <FieldInfo, object> >();
                    foreach (FileReference InputFile in InputFiles)
                    {
                        if (!TryReadFile(InputFile, CategoryToFields, TypeToValues, Schema))
                        {
                            throw new BuildException("Failed to properly read XML file : {0}", InputFile.FullName);
                        }
                    }

                    // Make sure the cache directory exists
                    DirectoryReference.CreateDirectory(CacheFile.Directory);

                    // Create the new cache
                    Values = new XmlConfigData(InputFiles, TypeToValues.ToDictionary(x => x.Key, x => x.Value.ToArray()));
                    Values.Write(CacheFile);
                }
            }

            // Apply all the static field values
            foreach (KeyValuePair <Type, KeyValuePair <FieldInfo, object>[]> TypeValuesPair in Values.TypeToValues)
            {
                foreach (KeyValuePair <FieldInfo, object> FieldValuePair in TypeValuesPair.Value)
                {
                    if (FieldValuePair.Key.IsStatic)
                    {
                        object Value = InstanceValue(FieldValuePair.Value, FieldValuePair.Key.FieldType);
                        FieldValuePair.Key.SetValue(null, Value);
                    }
                }
            }
        }
Exemple #27
0
        /// <summary>
        /// Upload all the files in the workspace for the current project
        /// </summary>
        void UploadWorkspace(DirectoryReference TempDir)
        {
            // Path to the scripts to be uploaded
            FileReference ScriptPathsFileName = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "Rsync", "RsyncEngineScripts.txt");

            // Read the list of scripts to be uploaded
            List <string> ScriptPaths = new List <string>();

            foreach (string Line in FileReference.ReadAllLines(ScriptPathsFileName))
            {
                string FileToUpload = Line.Trim();
                if (FileToUpload.Length > 0 && FileToUpload[0] != ';')
                {
                    ScriptPaths.Add(FileToUpload);
                }
            }

            // Fixup the line endings
            List <FileReference> TargetFiles = new List <FileReference>();

            foreach (string ScriptPath in ScriptPaths)
            {
                FileReference SourceFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, ScriptPath.TrimStart('/'));
                if (!FileReference.Exists(SourceFile))
                {
                    throw new BuildException("Missing script required for remote upload: {0}", SourceFile);
                }

                FileReference TargetFile = FileReference.Combine(TempDir, SourceFile.MakeRelativeTo(UnrealBuildTool.EngineDirectory));
                if (!FileReference.Exists(TargetFile) || FileReference.GetLastWriteTimeUtc(TargetFile) < FileReference.GetLastWriteTimeUtc(SourceFile))
                {
                    DirectoryReference.CreateDirectory(TargetFile.Directory);
                    string ScriptText = FileReference.ReadAllText(SourceFile);
                    FileReference.WriteAllText(TargetFile, ScriptText.Replace("\r\n", "\n"));
                }
                TargetFiles.Add(TargetFile);
            }

            // Write a file that protects all the scripts from being overridden by the standard engine filters
            FileReference ScriptUploadList = FileReference.Combine(TempDir, "RsyncEngineScripts-Upload.txt");

            using (StreamWriter Writer = new StreamWriter(ScriptUploadList.FullName))
            {
                foreach (string ScriptPath in ScriptPaths)
                {
                    for (int SlashIdx = ScriptPath.IndexOf('/', 1); SlashIdx != -1; SlashIdx = ScriptPath.IndexOf('/', SlashIdx + 1))
                    {
                        Writer.WriteLine("+ {0}", ScriptPath.Substring(0, SlashIdx));
                    }
                    Writer.WriteLine("+ {0}", ScriptPath);
                }
                Writer.WriteLine("protect *");
            }

            // Write a file that protects all the scripts from being overridden by the standard engine filters
            FileReference ScriptProtectList = FileReference.Combine(TempDir, "RsyncEngineScripts-Protect.txt");

            using (StreamWriter Writer = new StreamWriter(ScriptProtectList.FullName))
            {
                foreach (string ScriptPath in ScriptPaths)
                {
                    Writer.WriteLine("protect {0}", ScriptPath);
                }
            }

            // Upload these files to the remote
            List <FileReference> FilterLocations = new List <FileReference>();

            FilterLocations.Add(ScriptUploadList);
            UploadDirectory(TempDir, GetRemotePath(UnrealBuildTool.EngineDirectory), FilterLocations);

            // Upload the engine files
            List <FileReference> EngineFilters = new List <FileReference>();

            EngineFilters.Add(ScriptProtectList);
            EngineFilters.Add(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "Rsync", "RsyncEngine.txt"));
            UploadDirectory(UnrealBuildTool.EngineDirectory, GetRemotePath(UnrealBuildTool.EngineDirectory), EngineFilters);

            // Upload the project files
            if (ProjectFile != null && !ProjectFile.IsUnderDirectory(UnrealBuildTool.EngineDirectory))
            {
                List <FileReference> ProjectFilters = new List <FileReference>();

                FileReference CustomProjectFilter = FileReference.Combine(ProjectFile.Directory, "Build", "Rsync", "RsyncProject.txt");
                if (FileReference.Exists(CustomProjectFilter))
                {
                    ProjectFilters.Add(CustomProjectFilter);
                }
                ProjectFilters.Add(FileReference.Combine(UnrealBuildTool.EngineDirectory, "Build", "Rsync", "RsyncProject.txt"));

                UploadDirectory(ProjectFile.Directory, GetRemotePath(ProjectFile.Directory), ProjectFilters);
            }
        }
Exemple #28
0
        /// <summary>
        /// Execute the command, having obtained the appropriate mutex
        /// </summary>
        /// <param name="Arguments">Command line arguments</param>
        /// <returns>Exit code</returns>
        private int ExecuteInternal(CommandLineArguments Arguments)
        {
            // Read the target info
            WriteMetadataTargetInfo TargetInfo = BinaryFormatterUtils.Load <WriteMetadataTargetInfo>(Arguments.GetFileReference("-Input="));
            bool bNoManifestChanges            = Arguments.HasOption("-NoManifestChanges");
            int  VersionNumber = Arguments.GetInteger("-Version=");

            Arguments.CheckAllArgumentsUsed();

            // Make sure the version number is correct
            if (VersionNumber != CurrentVersionNumber)
            {
                throw new BuildException("Version number to WriteMetadataMode is incorrect (expected {0}, got {1})", CurrentVersionNumber, VersionNumber);
            }

            // Check if we need to set a build id
            TargetReceipt Receipt = TargetInfo.Receipt;

            if (String.IsNullOrEmpty(Receipt.Version.BuildId))
            {
                // Check if there's an existing version file. If it exists, try to merge in any manifests that are valid (and reuse the existing build id)
                BuildVersion PreviousVersion;
                if (TargetInfo.VersionFile != null && BuildVersion.TryRead(TargetInfo.VersionFile, out PreviousVersion))
                {
                    // Check if we can reuse the existing manifests. This prevents unnecessary builds when switching between projects.
                    Dictionary <FileReference, ModuleManifest> PreviousFileToManifest = new Dictionary <FileReference, ModuleManifest>();
                    if (TryRecyclingManifests(PreviousVersion.BuildId, TargetInfo.FileToManifest.Keys, PreviousFileToManifest))
                    {
                        // Merge files from the existing manifests with the new ones
                        foreach (KeyValuePair <FileReference, ModuleManifest> Pair in PreviousFileToManifest)
                        {
                            ModuleManifest TargetManifest = TargetInfo.FileToManifest[Pair.Key];
                            MergeManifests(Pair.Value, TargetManifest);
                        }

                        // Update the build id to use the current one
                        Receipt.Version.BuildId = PreviousVersion.BuildId;
                    }
                }

                // If the build id is still not set, generate a new one from a GUID
                if (String.IsNullOrEmpty(Receipt.Version.BuildId))
                {
                    Receipt.Version.BuildId = Guid.NewGuid().ToString();
                }
            }
            else
            {
                // Read all the manifests and merge them into the new ones, if they have the same build id
                foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
                {
                    ModuleManifest SourceManifest;
                    if (TryReadManifest(Pair.Key, out SourceManifest) && SourceManifest.BuildId == Receipt.Version.BuildId)
                    {
                        MergeManifests(SourceManifest, Pair.Value);
                    }
                }
            }

            // Update the build id in all the manifests, and write them out
            foreach (KeyValuePair <FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
            {
                FileReference ManifestFile = Pair.Key;
                if (!UnrealBuildTool.IsFileInstalled(ManifestFile))
                {
                    ModuleManifest Manifest = Pair.Value;
                    Manifest.BuildId = Receipt.Version.BuildId;

                    if (!FileReference.Exists(ManifestFile))
                    {
                        // If the file doesn't already exist, just write it out
                        DirectoryReference.CreateDirectory(ManifestFile.Directory);
                        Manifest.Write(ManifestFile);
                    }
                    else
                    {
                        // Otherwise write it to a buffer first
                        string OutputText;
                        using (StringWriter Writer = new StringWriter())
                        {
                            Manifest.Write(Writer);
                            OutputText = Writer.ToString();
                        }

                        // And only write it to disk if it's been modified. Note that if a manifest is out of date, we should have generated a new build id causing the contents to differ.
                        string CurrentText = FileReference.ReadAllText(ManifestFile);
                        if (CurrentText != OutputText)
                        {
                            if (bNoManifestChanges)
                            {
                                Log.TraceError("Build modifies {0}. This is not permitted. Before:\n    {1}\nAfter:\n    {2}", ManifestFile, CurrentText.Replace("\n", "\n    "), OutputText.Replace("\n", "\n    "));
                            }
                            else
                            {
                                FileReference.WriteAllText(ManifestFile, OutputText);
                            }
                        }
                    }
                }
            }

            // Write out the version file, if it's changed. Since this file is next to the executable, it may be used by multiple targets, and we should avoid modifying it unless necessary.
            if (TargetInfo.VersionFile != null && !UnrealBuildTool.IsFileInstalled(TargetInfo.VersionFile))
            {
                DirectoryReference.CreateDirectory(TargetInfo.VersionFile.Directory);

                StringWriter Writer = new StringWriter();
                Receipt.Version.Write(Writer);

                string Text = Writer.ToString();
                if (!FileReference.Exists(TargetInfo.VersionFile) || File.ReadAllText(TargetInfo.VersionFile.FullName) != Text)
                {
                    File.WriteAllText(TargetInfo.VersionFile.FullName, Text);
                }
            }

            // Write out the receipt
            if (!UnrealBuildTool.IsFileInstalled(TargetInfo.ReceiptFile))
            {
                DirectoryReference.CreateDirectory(TargetInfo.ReceiptFile.Directory);
                Receipt.Write(TargetInfo.ReceiptFile);
            }

            return(0);
        }
 /// <summary>
 /// Writes the state to disk
 /// </summary>
 /// <param name="Location">Location to write to</param>
 public void Save(FileReference Location)
 {
     DirectoryReference.CreateDirectory(Location.Directory);
     BinaryFormatterUtils.Save(Location, this);
 }
        protected override bool WriteMasterProjectFile(ProjectFile UBTProject, PlatformProjectGeneratorCollection PlatformProjectGenerators)
        {
            bool bSuccess = true;

            string SolutionFileName = MasterProjectName + ".sln";

            // Setup solution file content
            StringBuilder VCSolutionFileContent = new StringBuilder();

            // Solution file header. Note that a leading newline is required for file type detection to work correclty in the shell.
            if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2019)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio Version 16");
                VCSolutionFileContent.AppendLine("VisualStudioVersion = 16.0.28315.86");
                VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2017)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 15");
                VCSolutionFileContent.AppendLine("VisualStudioVersion = 15.0.25807.0");
                VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2015)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 14");
                VCSolutionFileContent.AppendLine("VisualStudioVersion = 14.0.22310.1");
                VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2013)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 2013");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2012)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 2012");
            }
            else
            {
                throw new BuildException("Unexpected ProjectFileFormat");
            }

            // Solution folders, files and project entries
            {
                // This the GUID that Visual Studio uses to identify a solution folder
                string SolutionFolderEntryGUID = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";

                // Solution folders
                {
                    List <MasterProjectFolder> AllSolutionFolders = new List <MasterProjectFolder>();
                    System.Action <List <MasterProjectFolder> /* Folders */> GatherFoldersFunction = null;
                    GatherFoldersFunction = FolderList =>
                    {
                        AllSolutionFolders.AddRange(FolderList);
                        foreach (MasterProjectFolder CurSubFolder in FolderList)
                        {
                            GatherFoldersFunction(CurSubFolder.SubFolders);
                        }
                    };
                    GatherFoldersFunction(RootFolder.SubFolders);

                    AllSolutionFolders.Sort((Lhs, Rhs) => Lhs.FolderName.CompareTo(Rhs.FolderName));

                    foreach (VisualStudioSolutionFolder CurFolder in AllSolutionFolders)
                    {
                        string FolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();
                        VCSolutionFileContent.AppendLine("Project(\"" + SolutionFolderEntryGUID + "\") = \"" + CurFolder.FolderName + "\", \"" + CurFolder.FolderName + "\", \"" + FolderGUIDString + "\"");

                        // Add any files that are inlined right inside the solution folder
                        if (CurFolder.Files.Count > 0)
                        {
                            VCSolutionFileContent.AppendLine("	ProjectSection(SolutionItems) = preProject");
                            foreach (string CurFile in CurFolder.Files)
                            {
                                // Syntax is:  <relative file path> = <relative file path>
                                VCSolutionFileContent.AppendLine("		"+ CurFile + " = " + CurFile);
                            }
                            VCSolutionFileContent.AppendLine("	EndProjectSection");
                        }

                        VCSolutionFileContent.AppendLine("EndProject");
                    }
                }


                // Project files
                List <MSBuildProjectFile> AllProjectFilesSorted = AllProjectFiles.OrderBy((ProjFile) => ProjFile.ProjectFilePath.GetFileNameWithoutExtension()).Cast <MSBuildProjectFile>().ToList();
                foreach (MSBuildProjectFile CurProject in AllProjectFiles)
                {
                    // Visual Studio uses different GUID types depending on the project type
                    string ProjectTypeGUID = CurProject.ProjectTypeGUID;

                    // NOTE: The project name in the solution doesn't actually *have* to match the project file name on disk.  However,
                    //       we prefer it when it does match so we use the actual file name here.
                    string ProjectNameInSolution = CurProject.ProjectFilePath.GetFileNameWithoutExtension();

                    // Use the existing project's GUID that's already known to us
                    string ProjectGUID = CurProject.ProjectGUID.ToString("B").ToUpperInvariant();

                    VCSolutionFileContent.AppendLine("Project(\"" + ProjectTypeGUID + "\") = \"" + ProjectNameInSolution + "\", \"" + CurProject.ProjectFilePath.MakeRelativeTo(ProjectFileGenerator.MasterProjectPath) + "\", \"" + ProjectGUID + "\"");

                    // Setup dependency on UnrealBuildTool, if we need that.  This makes sure that UnrealBuildTool is
                    // freshly compiled before kicking off any build operations on this target project
                    if (!CurProject.IsStubProject)
                    {
                        List <ProjectFile> Dependencies = new List <ProjectFile>();
                        if (CurProject.IsGeneratedProject && UBTProject != null && CurProject != UBTProject)
                        {
                            Dependencies.Add(UBTProject);
                            Dependencies.AddRange(UBTProject.DependsOnProjects);
                        }
                        Dependencies.AddRange(CurProject.DependsOnProjects);

                        if (Dependencies.Count > 0)
                        {
                            VCSolutionFileContent.AppendLine("\tProjectSection(ProjectDependencies) = postProject");

                            // Setup any addition dependencies this project has...
                            foreach (ProjectFile DependsOnProject in Dependencies)
                            {
                                string DependsOnProjectGUID = ((MSBuildProjectFile)DependsOnProject).ProjectGUID.ToString("B").ToUpperInvariant();
                                VCSolutionFileContent.AppendLine("\t\t" + DependsOnProjectGUID + " = " + DependsOnProjectGUID);
                            }

                            VCSolutionFileContent.AppendLine("\tEndProjectSection");
                        }
                    }

                    VCSolutionFileContent.AppendLine("EndProject");
                }

                // Get the path to the visualizers file. Try to make it relative to the solution directory, but fall back to a full path if it's a foreign project.
                FileReference VisualizersFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Extras", "VisualStudioDebugging", "UE4.natvis");

                // Add the visualizers at the solution level. Doesn't seem to be picked up from a makefile project in VS2017 15.8.5.
                VCSolutionFileContent.AppendLine(String.Format("Project(\"{{2150E333-8FDC-42A3-9474-1A3956D46DE8}}\") = \"Visualizers\", \"Visualizers\", \"{0}\"", Guid.NewGuid().ToString("B").ToUpperInvariant()));
                VCSolutionFileContent.AppendLine("\tProjectSection(SolutionItems) = preProject");
                VCSolutionFileContent.AppendLine("\t\t{0} = {0}", VisualizersFile.MakeRelativeTo(MasterProjectPath));
                VCSolutionFileContent.AppendLine("\tEndProjectSection");
                VCSolutionFileContent.AppendLine("EndProject");
            }

            // Solution configuration platforms.  This is just a list of all of the platforms and configurations that
            // appear in Visual Studio's build configuration selector.
            List <VCSolutionConfigCombination> SolutionConfigCombinations = new List <VCSolutionConfigCombination>();

            // The "Global" section has source control, solution configurations, project configurations,
            // preferences, and project hierarchy data
            {
                VCSolutionFileContent.AppendLine("Global");
                {
                    {
                        VCSolutionFileContent.AppendLine("	GlobalSection(SolutionConfigurationPlatforms) = preSolution");

                        Dictionary <string, Tuple <UnrealTargetConfiguration, TargetType> > SolutionConfigurationsValidForProjects = new Dictionary <string, Tuple <UnrealTargetConfiguration, TargetType> >();
                        HashSet <UnrealTargetPlatform> PlatformsValidForProjects = new HashSet <UnrealTargetPlatform>();

                        foreach (UnrealTargetConfiguration CurConfiguration in SupportedConfigurations)
                        {
                            if (InstalledPlatformInfo.IsValidConfiguration(CurConfiguration, EProjectType.Code))
                            {
                                foreach (UnrealTargetPlatform CurPlatform in SupportedPlatforms)
                                {
                                    if (InstalledPlatformInfo.IsValidPlatform(CurPlatform, EProjectType.Code))
                                    {
                                        foreach (ProjectFile CurProject in AllProjectFiles)
                                        {
                                            if (!CurProject.IsStubProject)
                                            {
                                                if (CurProject.ProjectTargets.Count == 0)
                                                {
                                                    throw new BuildException("Expecting project '" + CurProject.ProjectFilePath + "' to have at least one ProjectTarget associated with it!");
                                                }

                                                // Figure out the set of valid target configuration names
                                                foreach (ProjectTarget ProjectTarget in CurProject.ProjectTargets)
                                                {
                                                    if (VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, CurPlatform, CurConfiguration, PlatformProjectGenerators))
                                                    {
                                                        PlatformsValidForProjects.Add(CurPlatform);

                                                        // Default to a target configuration name of "Game", since that will collapse down to an empty string
                                                        TargetType TargetType = TargetType.Game;
                                                        if (ProjectTarget.TargetRules != null)
                                                        {
                                                            TargetType = ProjectTarget.TargetRules.Type;
                                                        }

                                                        string SolutionConfigName = MakeSolutionConfigurationName(CurConfiguration, TargetType);
                                                        SolutionConfigurationsValidForProjects[SolutionConfigName] = new Tuple <UnrealTargetConfiguration, TargetType>(CurConfiguration, TargetType);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        foreach (UnrealTargetPlatform CurPlatform in PlatformsValidForProjects)
                        {
                            foreach (KeyValuePair <string, Tuple <UnrealTargetConfiguration, TargetType> > SolutionConfigKeyValue in SolutionConfigurationsValidForProjects)
                            {
                                // e.g.  "Development|Win64 = Development|Win64"
                                string SolutionConfigName = SolutionConfigKeyValue.Key;
                                UnrealTargetConfiguration Configuration = SolutionConfigKeyValue.Value.Item1;
                                TargetType TargetType = SolutionConfigKeyValue.Value.Item2;

                                string SolutionPlatformName = CurPlatform.ToString();

                                string SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName;
                                SolutionConfigCombinations.Add(
                                    new VCSolutionConfigCombination
                                {
                                    VCSolutionConfigAndPlatformName = SolutionConfigAndPlatformPair,
                                    Configuration           = Configuration,
                                    Platform                = CurPlatform,
                                    TargetConfigurationName = TargetType
                                }
                                    );
                            }
                        }

                        // Sort the list of solution platform strings alphabetically (Visual Studio prefers it)
                        SolutionConfigCombinations.Sort(
                            new Comparison <VCSolutionConfigCombination>(
                                (x, y) => { return(String.Compare(x.VCSolutionConfigAndPlatformName, y.VCSolutionConfigAndPlatformName, StringComparison.InvariantCultureIgnoreCase)); }
                                )
                            );

                        HashSet <string> AppendedSolutionConfigAndPlatformNames = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);
                        foreach (VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations)
                        {
                            // We alias "Game" and "Program" to both have the same solution configuration, so we're careful not to add the same combination twice.
                            if (!AppendedSolutionConfigAndPlatformNames.Contains(SolutionConfigCombination.VCSolutionConfigAndPlatformName))
                            {
                                VCSolutionFileContent.AppendLine("		"+ SolutionConfigCombination.VCSolutionConfigAndPlatformName + " = " + SolutionConfigCombination.VCSolutionConfigAndPlatformName);
                                AppendedSolutionConfigAndPlatformNames.Add(SolutionConfigCombination.VCSolutionConfigAndPlatformName);
                            }
                        }

                        VCSolutionFileContent.AppendLine("	EndGlobalSection");
                    }


                    // Assign each project's "project configuration" to our "solution platform + configuration" pairs.  This
                    // also sets up which projects are actually built when building the solution.
                    {
                        VCSolutionFileContent.AppendLine("	GlobalSection(ProjectConfigurationPlatforms) = postSolution");

                        foreach (MSBuildProjectFile CurProject in AllProjectFiles)
                        {
                            foreach (VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations)
                            {
                                // Get the context for the current solution context
                                MSBuildProjectContext ProjectContext = CurProject.GetMatchingProjectContext(SolutionConfigCombination.TargetConfigurationName, SolutionConfigCombination.Configuration, SolutionConfigCombination.Platform, PlatformProjectGenerators);

                                // Write the solution mapping (e.g.  "{4232C52C-680F-4850-8855-DC39419B5E9B}.Debug|iOS.ActiveCfg = iOS_Debug|Win32")
                                string CurProjectGUID = CurProject.ProjectGUID.ToString("B").ToUpperInvariant();
                                VCSolutionFileContent.AppendLine("		{0}.{1}.ActiveCfg = {2}", CurProjectGUID, SolutionConfigCombination.VCSolutionConfigAndPlatformName, ProjectContext.Name);
                                if (ProjectContext.bBuildByDefault)
                                {
                                    VCSolutionFileContent.AppendLine("		{0}.{1}.Build.0 = {2}", CurProjectGUID, SolutionConfigCombination.VCSolutionConfigAndPlatformName, ProjectContext.Name);
                                    if (ProjectContext.bDeployByDefault)
                                    {
                                        VCSolutionFileContent.AppendLine("		{0}.{1}.Deploy.0 = {2}", CurProjectGUID, SolutionConfigCombination.VCSolutionConfigAndPlatformName, ProjectContext.Name);
                                    }
                                }
                            }
                        }

                        VCSolutionFileContent.AppendLine("	EndGlobalSection");
                    }


                    // Setup other solution properties
                    {
                        // HideSolutionNode sets whether or not the top-level solution entry is completely hidden in the UI.
                        // We don't want that, as we need users to be able to right click on the solution tree item.
                        VCSolutionFileContent.AppendLine("	GlobalSection(SolutionProperties) = preSolution");
                        VCSolutionFileContent.AppendLine("		HideSolutionNode = FALSE");
                        VCSolutionFileContent.AppendLine("	EndGlobalSection");
                    }



                    // Solution directory hierarchy
                    {
                        VCSolutionFileContent.AppendLine("	GlobalSection(NestedProjects) = preSolution");

                        // Every entry in this section is in the format "Guid1 = Guid2".  Guid1 is the child project (or solution
                        // filter)'s GUID, and Guid2 is the solution filter directory to parent the child project (or solution
                        // filter) to.  This sets up the hierarchical solution explorer tree for all solution folders and projects.

                        System.Action <StringBuilder /* VCSolutionFileContent */, List <MasterProjectFolder> /* Folders */> FolderProcessorFunction = null;
                        FolderProcessorFunction = (LocalVCSolutionFileContent, LocalMasterProjectFolders) =>
                        {
                            foreach (VisualStudioSolutionFolder CurFolder in LocalMasterProjectFolders)
                            {
                                string CurFolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();

                                foreach (MSBuildProjectFile ChildProject in CurFolder.ChildProjects)
                                {
                                    //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                    LocalVCSolutionFileContent.AppendLine("		"+ ChildProject.ProjectGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString);
                                }

                                foreach (VisualStudioSolutionFolder SubFolder in CurFolder.SubFolders)
                                {
                                    //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                    LocalVCSolutionFileContent.AppendLine("		"+ SubFolder.FolderGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString);
                                }

                                // Recurse into subfolders
                                FolderProcessorFunction(LocalVCSolutionFileContent, CurFolder.SubFolders);
                            }
                        };
                        FolderProcessorFunction(VCSolutionFileContent, RootFolder.SubFolders);

                        VCSolutionFileContent.AppendLine("	EndGlobalSection");
                    }
                }

                VCSolutionFileContent.AppendLine("EndGlobal");
            }


            // Save the solution file
            if (bSuccess)
            {
                string SolutionFilePath = FileReference.Combine(MasterProjectPath, SolutionFileName).FullName;
                bSuccess = WriteFileIfChanged(SolutionFilePath, VCSolutionFileContent.ToString());
            }


            // Save a solution config file which selects the development editor configuration by default.
            if (bSuccess && bWriteSolutionOptionFile)
            {
                // Figure out the filename for the SUO file. VS will automatically import the options from earlier versions if necessary.
                FileReference SolutionOptionsFileName;
                switch (ProjectFileFormat)
                {
                case VCProjectFileFormat.VisualStudio2012:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v11.suo"));
                    break;

                case VCProjectFileFormat.VisualStudio2013:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v12.suo"));
                    break;

                case VCProjectFileFormat.VisualStudio2015:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v14", ".suo");
                    break;

                case VCProjectFileFormat.VisualStudio2017:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v15", ".suo");
                    break;

                case VCProjectFileFormat.VisualStudio2019:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v15", ".suo");                             // Still uses v15
                    break;

                default:
                    throw new BuildException("Unsupported Visual Studio version");
                }

                // Check it doesn't exist before overwriting it. Since these files store the user's preferences, it'd be bad form to overwrite them.
                if (!FileReference.Exists(SolutionOptionsFileName))
                {
                    DirectoryReference.CreateDirectory(SolutionOptionsFileName.Directory);

                    VCSolutionOptions Options = new VCSolutionOptions(ProjectFileFormat);

                    // Set the default configuration and startup project
                    VCSolutionConfigCombination DefaultConfig = SolutionConfigCombinations.Find(x => x.Configuration == UnrealTargetConfiguration.Development && x.Platform == UnrealTargetPlatform.Win64 && x.TargetConfigurationName == TargetType.Editor);
                    if (DefaultConfig != null)
                    {
                        List <VCBinarySetting> Settings = new List <VCBinarySetting>();
                        Settings.Add(new VCBinarySetting("ActiveCfg", DefaultConfig.VCSolutionConfigAndPlatformName));
                        if (DefaultProject != null)
                        {
                            Settings.Add(new VCBinarySetting("StartupProject", ((MSBuildProjectFile)DefaultProject).ProjectGUID.ToString("B")));
                        }
                        Options.SetConfiguration(Settings);
                    }

                    // Mark all the projects as closed by default, apart from the startup project
                    VCSolutionExplorerState ExplorerState = new VCSolutionExplorerState();
                    if (ProjectFileFormat >= VCProjectFileFormat.VisualStudio2017)
                    {
                        BuildSolutionExplorerState_VS2017(RootFolder, "", ExplorerState, DefaultProject);
                    }
                    else
                    {
                        BuildSolutionExplorerState_VS2015(AllProjectFiles, ExplorerState, DefaultProject, IncludeEnginePrograms);
                    }
                    Options.SetExplorerState(ExplorerState);

                    // Write the file
                    if (Options.Sections.Count > 0)
                    {
                        Options.Write(SolutionOptionsFileName.FullName);
                    }
                }
            }

            return(bSuccess);
        }