Esempio n. 1
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>
        /// Patches a set of actions for use with live coding. The new action list will output object files to a different location.
        /// </summary>
        /// <param name="Actions">Set of actions</param>
        /// <param name="OriginalFileToPatchedFile">Dictionary that receives a map of original object file to patched object file</param>
        public static void PatchActionGraphForLiveCoding(IEnumerable <Action> Actions, Dictionary <FileReference, FileReference> OriginalFileToPatchedFile)
        {
            foreach (Action Action in Actions)
            {
                if (Action.ActionType == ActionType.Compile)
                {
                    if (!Action.CommandPath.GetFileName().Equals("cl-filter.exe", StringComparison.OrdinalIgnoreCase))
                    {
                        throw new BuildException("Unable to patch action graph - unexpected executable in compile action ({0})", Action.CommandPath);
                    }

                    List <string> Arguments = Utils.ParseArgumentList(Action.CommandArguments);

                    // Find the index of the cl-filter argument delimiter
                    int DelimiterIdx = Arguments.IndexOf("--");
                    if (DelimiterIdx == -1)
                    {
                        throw new BuildException("Unable to patch action graph - missing '--' delimiter to cl-filter");
                    }

                    // Fix the dependencies path
                    const string DependenciesPrefix = "-dependencies=";

                    int DependenciesIdx = 0;
                    for (;; DependenciesIdx++)
                    {
                        if (DependenciesIdx == DelimiterIdx)
                        {
                            throw new BuildException("Unable to patch action graph - missing '{0}' argument to cl-filter", DependenciesPrefix);
                        }
                        else if (Arguments[DependenciesIdx].StartsWith(DependenciesPrefix, StringComparison.OrdinalIgnoreCase))
                        {
                            break;
                        }
                    }

                    FileReference OldDependenciesFile     = new FileReference(Arguments[DependenciesIdx].Substring(DependenciesPrefix.Length));
                    FileItem      OldDependenciesFileItem = Action.ProducedItems.First(x => x.Location == OldDependenciesFile);
                    Action.ProducedItems.Remove(OldDependenciesFileItem);

                    FileReference NewDependenciesFile     = OldDependenciesFile.ChangeExtension(".lc.response");
                    FileItem      NewDependenciesFileItem = FileItem.GetItemByFileReference(NewDependenciesFile);
                    Action.ProducedItems.Add(NewDependenciesFileItem);

                    Arguments[DependenciesIdx] = DependenciesPrefix + NewDependenciesFile.FullName;

                    // Fix the response file
                    int ResponseFileIdx = DelimiterIdx + 1;
                    for (; ; ResponseFileIdx++)
                    {
                        if (ResponseFileIdx == Arguments.Count)
                        {
                            throw new BuildException("Unable to patch action graph - missing response file argument to cl-filter");
                        }
                        else if (Arguments[ResponseFileIdx].StartsWith("@", StringComparison.Ordinal))
                        {
                            break;
                        }
                    }

                    FileReference OldResponseFile = new FileReference(Arguments[ResponseFileIdx].Substring(1));
                    FileReference NewResponseFile = new FileReference(OldResponseFile.FullName + ".lc");

                    const string OutputFilePrefix = "/Fo";

                    string[] ResponseLines = FileReference.ReadAllLines(OldResponseFile);
                    for (int Idx = 0; Idx < ResponseLines.Length; Idx++)
                    {
                        string ResponseLine = ResponseLines[Idx];
                        if (ResponseLine.StartsWith(OutputFilePrefix, StringComparison.Ordinal))
                        {
                            FileReference OldOutputFile     = new FileReference(ResponseLine.Substring(3).Trim('\"'));
                            FileItem      OldOutputFileItem = Action.ProducedItems.First(x => x.Location == OldOutputFile);
                            Action.ProducedItems.Remove(OldOutputFileItem);

                            FileReference NewOutputFile     = OldOutputFile.ChangeExtension(".lc.obj");
                            FileItem      NewOutputFileItem = FileItem.GetItemByFileReference(NewOutputFile);
                            Action.ProducedItems.Add(NewOutputFileItem);

                            OriginalFileToPatchedFile[OldOutputFile] = NewOutputFile;

                            ResponseLines[Idx] = OutputFilePrefix + "\"" + NewOutputFile.FullName + "\"";
                            break;
                        }
                    }
                    FileReference.WriteAllLines(NewResponseFile, ResponseLines);

                    Arguments[ResponseFileIdx] = "@" + NewResponseFile.FullName;

                    // Update the final arguments
                    Action.CommandArguments = Utils.FormatCommandLine(Arguments);
                }
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Dynamically compiles an assembly for the specified source file and loads that assembly into the application's
        /// current domain.  If an assembly has already been compiled and is not out of date, then it will be loaded and
        /// no compilation is necessary.
        /// </summary>
        /// <param name="OutputAssemblyPath">Full path to the assembly to be created</param>
        /// <param name="SourceFileNames">List of source file name</param>
        /// <param name="ReferencedAssembies"></param>
        /// <param name="PreprocessorDefines"></param>
        /// <param name="DoNotCompile"></param>
        /// <param name="TreatWarningsAsErrors"></param>
        /// <returns>The assembly that was loaded</returns>
        public static Assembly CompileAndLoadAssembly(FileReference OutputAssemblyPath, HashSet <FileReference> SourceFileNames, List <string> ReferencedAssembies = null, List <string> PreprocessorDefines = null, bool DoNotCompile = false, bool TreatWarningsAsErrors = false)
        {
            // Check to see if the resulting assembly is compiled and up to date
            FileReference AssemblySourcesListFilePath = FileReference.Combine(OutputAssemblyPath.Directory, Path.GetFileNameWithoutExtension(OutputAssemblyPath.FullName) + "SourceFiles.txt");
            bool          bNeedsCompilation           = false;

            if (!DoNotCompile)
            {
                bNeedsCompilation = RequiresCompilation(SourceFileNames, AssemblySourcesListFilePath, OutputAssemblyPath);
            }

            // Load the assembly to ensure it is correct
            Assembly CompiledAssembly = null;

            if (!bNeedsCompilation)
            {
                try
                {
                    // Load the previously-compiled assembly from disk
                    CompiledAssembly = Assembly.LoadFile(OutputAssemblyPath.FullName);
                }
                catch (FileLoadException Ex)
                {
                    Log.TraceInformation(String.Format("Unable to load the previously-compiled assembly file '{0}'.  Unreal Build Tool will try to recompile this assembly now.  (Exception: {1})", OutputAssemblyPath, Ex.Message));
                    bNeedsCompilation = true;
                }
                catch (BadImageFormatException Ex)
                {
                    Log.TraceInformation(String.Format("Compiled assembly file '{0}' appears to be for a newer CLR version or is otherwise invalid.  Unreal Build Tool will try to recompile this assembly now.  (Exception: {1})", OutputAssemblyPath, Ex.Message));
                    bNeedsCompilation = true;
                }
                catch (FileNotFoundException)
                {
                    throw new BuildException("Precompiled rules assembly '{0}' does not exist.", OutputAssemblyPath);
                }
                catch (Exception Ex)
                {
                    throw new BuildException(Ex, "Error while loading previously-compiled assembly file '{0}'.  (Exception: {1})", OutputAssemblyPath, Ex.Message);
                }
            }

            // Compile the assembly if me
            if (bNeedsCompilation)
            {
                using (Timeline.ScopeEvent(String.Format("Compiling rules assembly ({0})", OutputAssemblyPath.GetFileName())))
                {
                    CompiledAssembly = CompileAssembly(OutputAssemblyPath, SourceFileNames, ReferencedAssembies, PreprocessorDefines, TreatWarningsAsErrors);
                }

                // Save out a list of all the source files we compiled.  This is so that we can tell if whole files were added or removed
                // since the previous time we compiled the assembly.  In that case, we'll always want to recompile it!
                FileReference.WriteAllLines(AssemblySourcesListFilePath, SourceFileNames.Select(x => x.FullName));
            }

#if !NET_CORE
            // Load the assembly into our app domain
            try
            {
                AppDomain.CurrentDomain.Load(CompiledAssembly.GetName());
            }
            catch (Exception Ex)
            {
                throw new BuildException(Ex, "Unable to load the compiled build assembly '{0}' into our application's domain.  (Exception: {1})", OutputAssemblyPath, Ex.Message);
            }
#endif

            return(CompiledAssembly);
        }
        /// <summary>
        /// Determine what needs to be built for a target
        /// </summary>
        /// <param name="BuildConfiguration">The build configuration</param>
        /// <param name="TargetDescriptor">Target being built</param>
        /// <param name="Makefile">Makefile generated for this target</param>
        /// <returns>Set of actions to execute</returns>
        static HashSet <Action> GetActionsForTarget(BuildConfiguration BuildConfiguration, TargetDescriptor TargetDescriptor, TargetMakefile Makefile)
        {
            // Create the action graph
            ActionGraph.Link(Makefile.Actions);

            // Get the hot-reload mode
            HotReloadMode HotReloadMode = TargetDescriptor.HotReloadMode;

            if (HotReloadMode == HotReloadMode.Default)
            {
                if (TargetDescriptor.HotReloadModuleNameToSuffix.Count > 0 && TargetDescriptor.ForeignPlugin == null)
                {
                    HotReloadMode = HotReloadMode.FromEditor;
                }
                else if (BuildConfiguration.bAllowHotReloadFromIDE && HotReload.ShouldDoHotReloadFromIDE(BuildConfiguration, TargetDescriptor))
                {
                    HotReloadMode = HotReloadMode.FromIDE;
                }
                else
                {
                    HotReloadMode = HotReloadMode.Disabled;
                }
            }

            // Guard against a live coding session for this target being active
            if (HotReloadMode != HotReloadMode.LiveCoding && TargetDescriptor.ForeignPlugin == null && HotReload.IsLiveCodingSessionActive(Makefile))
            {
                throw new BuildException("Unable to start regular build while Live Coding is active. Press Ctrl+Alt+F11 to trigger a Live Coding compile.");
            }

            // Get the root prerequisite actions
            List <Action> PrerequisiteActions = GatherPrerequisiteActions(TargetDescriptor, Makefile);

            // Get the path to the hot reload state file for this target
            FileReference HotReloadStateFile = global::UnrealBuildTool.HotReloadState.GetLocation(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, TargetDescriptor.Architecture);

            // Apply the previous hot reload state
            HotReloadState HotReloadState = null;

            if (HotReloadMode == HotReloadMode.Disabled)
            {
                // Make sure we're not doing a partial build from the editor (eg. compiling a new plugin)
                if (TargetDescriptor.ForeignPlugin == null && TargetDescriptor.SingleFileToCompile == null)
                {
                    // Delete the previous state file
                    HotReload.DeleteTemporaryFiles(HotReloadStateFile);
                }
            }
            else
            {
                // Read the previous state file and apply it to the action graph
                if (FileReference.Exists(HotReloadStateFile))
                {
                    HotReloadState = HotReloadState.Load(HotReloadStateFile);
                }
                else
                {
                    HotReloadState = new HotReloadState();
                }

                // Apply the old state to the makefile
                HotReload.ApplyState(HotReloadState, Makefile);

                // If we want a specific suffix on any modules, apply that now. We'll track the outputs later, but the suffix has to be forced (and is always out of date if it doesn't exist).
                HotReload.PatchActionGraphWithNames(PrerequisiteActions, TargetDescriptor.HotReloadModuleNameToSuffix, Makefile);

                // Re-link the action graph
                ActionGraph.Link(PrerequisiteActions);
            }

            // Create the dependencies cache
            CppDependencyCache CppDependencies;

            using (Timeline.ScopeEvent("Reading dependency cache"))
            {
                CppDependencies = CppDependencyCache.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, Makefile.TargetType, TargetDescriptor.Architecture);
            }

            // Create the action history
            ActionHistory History;

            using (Timeline.ScopeEvent("Reading action history"))
            {
                History = ActionHistory.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, Makefile.TargetType, TargetDescriptor.Architecture);
            }

            // Plan the actions to execute for the build. For single file compiles, always rebuild the source file regardless of whether it's out of date.
            HashSet <Action> TargetActionsToExecute;

            if (TargetDescriptor.SingleFileToCompile == null)
            {
                TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries);
            }
            else
            {
                TargetActionsToExecute = new HashSet <Action>(PrerequisiteActions);
            }

            // Additional processing for hot reload
            if (HotReloadMode == HotReloadMode.LiveCoding)
            {
                // Make sure we're not overwriting any lazy-loaded modules
                if (TargetDescriptor.LiveCodingModules != null)
                {
                    // Read the list of modules that we're allowed to build
                    string[] Lines = FileReference.ReadAllLines(TargetDescriptor.LiveCodingModules);

                    // Parse it out into a set of filenames
                    HashSet <string> AllowedOutputFileNames = new HashSet <string>(FileReference.Comparer);
                    foreach (string Line in Lines)
                    {
                        string TrimLine = Line.Trim();
                        if (TrimLine.Length > 0)
                        {
                            AllowedOutputFileNames.Add(Path.GetFileName(TrimLine));
                        }
                    }

                    // Find all the binaries that we're actually going to build
                    HashSet <FileReference> OutputFiles = new HashSet <FileReference>();
                    foreach (Action Action in TargetActionsToExecute)
                    {
                        if (Action.ActionType == ActionType.Link)
                        {
                            OutputFiles.UnionWith(Action.ProducedItems.Where(x => x.HasExtension(".exe") || x.HasExtension(".dll")).Select(x => x.Location));
                        }
                    }

                    // Find all the files that will be built that aren't allowed
                    List <FileReference> ProtectedOutputFiles = OutputFiles.Where(x => !AllowedOutputFileNames.Contains(x.GetFileName())).ToList();
                    if (ProtectedOutputFiles.Count > 0)
                    {
                        FileReference.WriteAllLines(new FileReference(TargetDescriptor.LiveCodingModules.FullName + ".out"), ProtectedOutputFiles.Select(x => x.ToString()));
                        foreach (FileReference ProtectedOutputFile in ProtectedOutputFiles)
                        {
                            Log.TraceInformation("Module {0} is not currently enabled for Live Coding", ProtectedOutputFile);
                        }
                        throw new CompilationResultException(CompilationResult.Canceled);
                    }
                }

                // Filter the prerequisite actions down to just the compile actions, then recompute all the actions to execute
                PrerequisiteActions    = new List <Action>(TargetActionsToExecute.Where(x => x.ActionType == ActionType.Compile));
                TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries);

                // Update the action graph with these new paths
                Dictionary <FileReference, FileReference> OriginalFileToPatchedFile = new Dictionary <FileReference, FileReference>();
                HotReload.PatchActionGraphForLiveCoding(PrerequisiteActions, OriginalFileToPatchedFile);

                // Get a new list of actions to execute now that the graph has been modified
                TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries);

                // Output the Live Coding manifest
                if (TargetDescriptor.LiveCodingManifest != null)
                {
                    HotReload.WriteLiveCodingManifest(TargetDescriptor.LiveCodingManifest, Makefile.Actions, OriginalFileToPatchedFile);
                }
            }
            else if (HotReloadMode == HotReloadMode.FromEditor || HotReloadMode == HotReloadMode.FromIDE)
            {
                // Patch action history for hot reload when running in assembler mode.  In assembler mode, the suffix on the output file will be
                // the same for every invocation on that makefile, but we need a new suffix each time.

                // For all the hot-reloadable modules that may need a unique suffix appended, build a mapping from output item to all the output items in that module. We can't
                // apply a suffix to one without applying a suffix to all of them.
                Dictionary <FileItem, FileItem[]> HotReloadItemToDependentItems = new Dictionary <FileItem, FileItem[]>();
                foreach (string HotReloadModuleName in Makefile.HotReloadModuleNames)
                {
                    int ModuleSuffix;
                    if (!TargetDescriptor.HotReloadModuleNameToSuffix.TryGetValue(HotReloadModuleName, out ModuleSuffix) || ModuleSuffix == -1)
                    {
                        FileItem[] ModuleOutputItems;
                        if (Makefile.ModuleNameToOutputItems.TryGetValue(HotReloadModuleName, out ModuleOutputItems))
                        {
                            foreach (FileItem ModuleOutputItem in ModuleOutputItems)
                            {
                                HotReloadItemToDependentItems[ModuleOutputItem] = ModuleOutputItems;
                            }
                        }
                    }
                }

                // Expand the list of actions to execute to include everything that references any files with a new suffix. Unlike a regular build, we can't ignore
                // dependencies on import libraries under the assumption that a header would change if the API changes, because the dependency will be on a different DLL.
                HashSet <FileItem> FilesRequiringSuffix = new HashSet <FileItem>(TargetActionsToExecute.SelectMany(x => x.ProducedItems).Where(x => HotReloadItemToDependentItems.ContainsKey(x)));
                for (int LastNumFilesWithNewSuffix = 0; FilesRequiringSuffix.Count > LastNumFilesWithNewSuffix;)
                {
                    LastNumFilesWithNewSuffix = FilesRequiringSuffix.Count;
                    foreach (Action PrerequisiteAction in PrerequisiteActions)
                    {
                        if (!TargetActionsToExecute.Contains(PrerequisiteAction))
                        {
                            foreach (FileItem ProducedItem in PrerequisiteAction.ProducedItems)
                            {
                                FileItem[] DependentItems;
                                if (HotReloadItemToDependentItems.TryGetValue(ProducedItem, out DependentItems))
                                {
                                    TargetActionsToExecute.Add(PrerequisiteAction);
                                    FilesRequiringSuffix.UnionWith(DependentItems);
                                }
                            }
                        }
                    }
                }

                // Build a list of file mappings
                Dictionary <FileReference, FileReference> OldLocationToNewLocation = new Dictionary <FileReference, FileReference>();
                foreach (FileItem FileRequiringSuffix in FilesRequiringSuffix)
                {
                    FileReference OldLocation = FileRequiringSuffix.Location;
                    FileReference NewLocation = HotReload.ReplaceSuffix(OldLocation, HotReloadState.NextSuffix);
                    OldLocationToNewLocation[OldLocation] = NewLocation;
                }

                // Update the action graph with these new paths
                HotReload.PatchActionGraph(PrerequisiteActions, OldLocationToNewLocation);

                // Get a new list of actions to execute now that the graph has been modified
                TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries);

                // Build a mapping of all file items to their original
                Dictionary <FileReference, FileReference> HotReloadFileToOriginalFile = new Dictionary <FileReference, FileReference>();
                foreach (KeyValuePair <FileReference, FileReference> Pair in HotReloadState.OriginalFileToHotReloadFile)
                {
                    HotReloadFileToOriginalFile[Pair.Value] = Pair.Key;
                }
                foreach (KeyValuePair <FileReference, FileReference> Pair in OldLocationToNewLocation)
                {
                    FileReference OriginalLocation;
                    if (!HotReloadFileToOriginalFile.TryGetValue(Pair.Key, out OriginalLocation))
                    {
                        OriginalLocation = Pair.Key;
                    }
                    HotReloadFileToOriginalFile[Pair.Value] = OriginalLocation;
                }

                // Now filter out all the hot reload files and update the state
                foreach (Action Action in TargetActionsToExecute)
                {
                    foreach (FileItem ProducedItem in Action.ProducedItems)
                    {
                        FileReference OriginalLocation;
                        if (HotReloadFileToOriginalFile.TryGetValue(ProducedItem.Location, out OriginalLocation))
                        {
                            HotReloadState.OriginalFileToHotReloadFile[OriginalLocation] = ProducedItem.Location;
                            HotReloadState.TemporaryFiles.Add(ProducedItem.Location);
                        }
                    }
                }

                // Increment the suffix for the next iteration
                if (TargetActionsToExecute.Count > 0)
                {
                    HotReloadState.NextSuffix++;
                }

                // Save the new state
                HotReloadState.Save(HotReloadStateFile);

                // Prevent this target from deploying
                Makefile.bDeployAfterCompile = false;
            }

            return(TargetActionsToExecute);
        }
Esempio n. 5
0
 public void Write(FileReference File)
 {
     Lines[Lines.Count - 1] = Lines[Lines.Count - 1].TrimEnd(',');
     FileReference.WriteAllLines(File, Lines.ToArray());
 }