예제 #1
0
        private FullSymbol GetFullSymbolFromProject(ProjectWithPredictions project)
        {
            // We construct the name of the value using the project name and its global properties
            // Observe this symbol has to be unique wrt another symbol coming from the same physical project (i.e. same project full
            // path) but different global properties. The project full path is already being passed as part of the 'key' when creating the
            // pip construction helper
            var valueName = PipConstructionUtilities.SanitizeStringForSymbol(project.FullPath.GetName(PathTable).ToString(m_context.StringTable));

            // If global properties are present, we append to the value name a flatten representation of them
            // There should always be a 'IsGraphBuild' property, so we count > 1
            Contract.Assert(project.GlobalProperties.ContainsKey(s_isGraphBuildProperty));
            if (project.GlobalProperties.Count > 1)
            {
                valueName += ".";
            }
            valueName += string.Join(
                ".",
                project.GlobalProperties
                // let's sort global properties keys to make sure the same string is generated consistently
                // case-sensitivity is already handled (and ignored) by GlobalProperties class
                .Where(kvp => kvp.Key != s_isGraphBuildProperty)
                .OrderBy(kvp => kvp.Key, StringComparer.Ordinal)
                .Select(gp => $"{PipConstructionUtilities.SanitizeStringForSymbol(gp.Key)}_{PipConstructionUtilities.SanitizeStringForSymbol(gp.Value)}"));

            var fullSymbol = FullSymbol.Create(m_context.SymbolTable, valueName);

            return(fullSymbol);
        }
예제 #2
0
        private void ConfigureProcessBuilder(
            ProcessBuilder processBuilder,
            RushProject project)
        {
            SetCmdTool(processBuilder, project);

            // Working directory - the directory where the project file lives.
            processBuilder.WorkingDirectory = DirectoryArtifact.CreateWithZeroPartialSealId(project.ProjectFolder);

            // We allow undeclared inputs to be read
            processBuilder.Options |= Process.Options.AllowUndeclaredSourceReads;

            // We want to enforce the use of weak fingerprint augmentation since input predictions could be not complete/sufficient
            // to avoid a large number of path sets
            processBuilder.Options |= Process.Options.EnforceWeakFingerprintAugmentation;

            // By default the double write policy is to allow same content double writes.
            processBuilder.DoubleWritePolicy |= DoubleWritePolicy.AllowSameContentDoubleWrites;

            PipConstructionUtilities.UntrackUserConfigurableArtifacts(processBuilder, m_resolverSettings);

            var logDirectory = GetLogDirectory(project);
            var logFile      = logDirectory.Combine(PathTable, "build.log");

            // Execute the build command and redirect the output to a designated log file
            processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromString("/C"));
            processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromString(project.BuildCommand));
            processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromString(">"));
            processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromAbsolutePath(logFile));
            processBuilder.AddOutputFile(logFile, FileExistence.Required);

            FrontEndUtilities.SetProcessEnvironmentVariables(CreateEnvironment(project), m_userDefinedPassthroughVariables, processBuilder, m_context.PathTable);
        }
예제 #3
0
        private AbsolutePath GetLogDirectory(ProjectWithPredictions projectFile, Qualifier qualifier)
        {
            var success = Root.TryGetRelative(PathTable, projectFile.FullPath, out var inFolderPathFromEnlistmentRoot);

            Contract.Assert(success);

            // We hardcode the log to go under the output directory Logs/MSBuild (and follow the project structure underneath)
            // The 'official' log directory (defined by Configuration.Logging) is not stable in CloudBuild across machines, and therefore it would
            // introduce cache misses
            var result = m_frontEndHost.Configuration.Layout.OutputDirectory
                         .Combine(PathTable, "Logs")
                         .Combine(PathTable, "MSBuild")
                         .Combine(PathTable, inFolderPathFromEnlistmentRoot);

            // Build a string with just the qualifier and global property values (e.g. 'debug-x86'). That should be unique enough.
            // Projects can be evaluated multiple times with different global properties (but same qualifiers), so just
            // the qualifier name is not enough
            List <string> values = qualifier.Values.Select(value => value.ToString(m_context.StringTable))
                                   .Union(projectFile.GlobalProperties.Where(kvp => kvp.Key != s_isGraphBuildProperty).Select(kvp => kvp.Value))
                                   .Select(value => PipConstructionUtilities.SanitizeStringForSymbol(value))
                                   .OrderBy(value => value, StringComparer.Ordinal) // Let's make sure we always produce the same string for the same set of values
                                   .ToList();

            if (values.Count > 0)
            {
                var valueIdentifier = string.Join("-", values);
                result = result.Combine(PathTable, valueIdentifier);
            }

            return(result);
        }
예제 #4
0
        /// <summary>
        /// Configures the process builder to execute the specified commands
        /// </summary>
        protected virtual void ConfigureProcessBuilder(
            ProcessBuilder processBuilder,
            JavaScriptProject project)
        {
            SetCmdTool(processBuilder, project);

            // Working directory - the directory where the project file lives.
            processBuilder.WorkingDirectory = DirectoryArtifact.CreateWithZeroPartialSealId(project.ProjectFolder);

            // We allow undeclared inputs to be read
            processBuilder.Options |= Process.Options.AllowUndeclaredSourceReads;

            // We want to enforce the use of weak fingerprint augmentation since input predictions could be not complete/sufficient
            // to avoid a large number of path sets
            processBuilder.Options |= Process.Options.EnforceWeakFingerprintAugmentation;

            // Try to preserve path set casing since many JavaScript projects deal with paths in a case-sensitive way
            // Otherwise in Windows we force path sets to be all uppercase
            processBuilder.Options |= Process.Options.PreservePathSetCasing;

            // By default the double write policy is to allow same content double writes.
            processBuilder.DoubleWritePolicy |= DoubleWritePolicy.AllowSameContentDoubleWrites;

            // Untrack the user profile. The corresponding mount is already configured for not tracking source files, and with allowed undeclared source reads,
            // any attempt to read into the user profile will fail to compute its corresponding hash
            processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.UserProfile)));

            // Add the associated build script name as a tag, so filtering on 'build' or 'test' can happen
            processBuilder.Tags = ReadOnlyArray <StringId> .FromWithoutCopy(new[] { StringId.Create(m_context.StringTable, project.ScriptCommandName) });

            PipConstructionUtilities.UntrackUserConfigurableArtifacts(processBuilder, m_resolverSettings);

            var logDirectory = GetLogDirectory(project);

            processBuilder.SetStandardOutputFile(logDirectory.Combine(m_context.PathTable, "build.log"));
            processBuilder.SetStandardErrorFile(logDirectory.Combine(m_context.PathTable, "error.log"));

            using (processBuilder.ArgumentsBuilder.StartFragment(PipDataFragmentEscaping.CRuntimeArgumentRules, " "))
            {
                processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromString("/C"));

                using (processBuilder.ArgumentsBuilder.StartFragment(PipDataFragmentEscaping.NoEscaping, " "))
                {
                    // Execute the command and redirect the output to a designated log file
                    processBuilder.ArgumentsBuilder.Add(PipDataAtom.FromString(project.ScriptCommand));

                    // If we need to append arguments to the script command, do it here
                    if (m_customCommands.TryGetValue(project.ScriptCommandName, out IReadOnlyList <JavaScriptArgument> extraArguments))
                    {
                        foreach (JavaScriptArgument value in extraArguments)
                        {
                            AddJavaScriptArgumentToBuilder(processBuilder.ArgumentsBuilder, value);
                        }
                    }
                }
            }

            FrontEndUtilities.SetProcessEnvironmentVariables(CreateEnvironment(project), m_userDefinedPassthroughVariables, processBuilder, m_context.PathTable);
        }
예제 #5
0
        private FullSymbol GetFullSymbolFromProject(RushProject project)
        {
            // We construct the name of the value using the project name
            var valueName = PipConstructionUtilities.SanitizeStringForSymbol(project.Name);

            var fullSymbol = FullSymbol.Create(m_context.SymbolTable, valueName);

            return(fullSymbol);
        }
        /// <summary>
        /// Used also for tests to discover processes based on project names
        /// </summary>
        internal static FullSymbol GetFullSymbolFromProject(string projectName, string scriptCommandName, SymbolTable symbolTable)
        {
            // We construct the name of the value using the project name
            var valueName = PipConstructionUtilities.SanitizeStringForSymbol($"{projectName}_{scriptCommandName}");

            var fullSymbol = FullSymbol.Create(symbolTable, valueName);

            return(fullSymbol);
        }
예제 #7
0
        private void SetEnvironmentVariables(ProcessBuilder processBuilder, NinjaNode node)
        {
            foreach (KeyValuePair <string, string> kvp in m_environmentVariables.Value)
            {
                if (kvp.Value != null)
                {
                    var envPipData = new PipDataBuilder(m_context.StringTable);
                    if (SpecialEnvironmentVariables.PassThroughPrefixes.All(prefix => !kvp.Key.StartsWith(prefix)))
                    {
                        envPipData.Add(kvp.Value);
                        processBuilder.SetEnvironmentVariable(StringId.Create(m_context.StringTable, kvp.Key), envPipData.ToPipData(string.Empty, PipDataFragmentEscaping.NoEscaping));
                    }
                }
            }

            foreach (var kvp in m_passThroughEnvironmentVariables.Value)
            {
                processBuilder.SetPassthroughEnvironmentVariable(StringId.Create(m_context.StringTable, kvp.Key));
            }

            foreach (var envVar in SpecialEnvironmentVariables.CloudBuildEnvironment)
            {
                processBuilder.SetPassthroughEnvironmentVariable(StringId.Create(m_context.StringTable, envVar));
            }

            // GlobalUnsafePassthroughEnvironmentVariables
            processBuilder.SetGlobalPassthroughEnvironmentVariable(m_frontEndHost.Configuration.FrontEnd.GlobalUnsafePassthroughEnvironmentVariables, m_context.StringTable);

            // We will specify a different MSPDBSRV endpoint for every pip.
            // This means every pip that needs to communicate to MSPDBSRV will
            // spawn a different child process.
            // This is because if two pips use the same endpoint at the same time
            // then second one will fail after the first one finishes, because the
            // first one was the one that spawned MSPDBSRV.EXE as a child
            // (which gets killed).
            //
            // IMPORTANT: This will cause the build to fail if two pips target the same PDB file.
            // Both LINK.EXE and CL.EXE can use MSPDBSRV.EXE:
            //   - If all linkers specify a different /pdb argument (or don't specify this argument and
            //   are linking programs with different names
            //   [https://docs.microsoft.com/en-us/cpp/build/reference/debug-generate-debug-info])
            //   then this won't happen
            //
            //   - We're forcing the compiler to not output to PDBs (/Z7)
            //
            // so this should work in the normal cases.
            var mspdbsrvPipDataBuilder = new PipDataBuilder(m_context.StringTable);

            mspdbsrvPipDataBuilder.Add(PipConstructionUtilities.ComputeSha256(node.Command));   // Unique value for each pip
            processBuilder.SetEnvironmentVariable(
                StringId.Create(m_context.StringTable, SpecialEnvironmentVariables.MsPdvSrvEndpoint),
                mspdbsrvPipDataBuilder.ToPipData(string.Empty, PipDataFragmentEscaping.NoEscaping));
        }
예제 #8
0
        public void RedirectedUserProfileSanitizesScriptCommandName(string scriptCommandName)
        {
            var project = CreateRushProject(scriptCommandName: scriptCommandName);
            var processOutputDirectories = Start()
                                           .Add(project)
                                           .ScheduleAll()
                                           .RetrieveSuccessfulProcess(project)
                                           .DirectoryOutputs;

            string sanitizedPath = PipConstructionUtilities.SanitizeStringForSymbol(scriptCommandName);

            XAssert.IsTrue(processOutputDirectories.Any(outputDirectory => outputDirectory.Path.ToString(PathTable).Contains(sanitizedPath)));
        }
        private AbsolutePath GetLogDirectory(JavaScriptProject projectFile)
        {
            var success = Root.TryGetRelative(PathTable, projectFile.ProjectFolder, out var inFolderPathFromEnlistmentRoot);

            Contract.Assert(success, $"Configuration root '{Root.ToString(PathTable)}' should be a parent of '{projectFile.ProjectFolder.ToString(PathTable)}'");

            // We hardcode the log to go under the output directory Logs/<frontend-name> (and follow the project structure underneath)
            var result = LogDirectoryBase(m_frontEndHost.Configuration, m_context.PathTable, m_resolverSettings.Name)
                         .Combine(PathTable, inFolderPathFromEnlistmentRoot)
                         .Combine(PathTable, PipConstructionUtilities.SanitizeStringForSymbol(projectFile.Name))
                         .Combine(PathTable, PipConstructionUtilities.SanitizeStringForSymbol(projectFile.ScriptCommandName));

            return(result);
        }
예제 #10
0
        private AbsolutePath GetLogDirectory(RushProject projectFile)
        {
            var success = Root.TryGetRelative(PathTable, projectFile.ProjectFolder, out var inFolderPathFromEnlistmentRoot);

            Contract.Assert(success, $"Configuration root '{Root.ToString(PathTable)}' should be a parent of '{projectFile.ProjectFolder.ToString(PathTable)}'");

            // We hardcode the log to go under the output directory Logs/Rush (and follow the project structure underneath)
            // The 'official' log directory (defined by Configuration.Logging) is not stable in CloudBuild across machines, and therefore it would
            // introduce cache misses
            var result = LogDirectoryBase(m_frontEndHost.Configuration, m_context.PathTable)
                         .Combine(PathTable, inFolderPathFromEnlistmentRoot)
                         .Combine(PathTable, PipConstructionUtilities.SanitizeStringForSymbol(projectFile.Name))
                         .Combine(PathTable, PipConstructionUtilities.SanitizeStringForSymbol(projectFile.ScriptCommandName));

            return(result);
        }
예제 #11
0
        /// <summary>
        /// Computes a sha256 based on the project properties
        /// </summary>
        private string ComputeSha256(ProjectWithPredictions projectWithPredictions)
        {
            using (var builderWrapper = Pools.GetStringBuilder())
            {
                // full path, global properties and targets to execute should uniquely identify the project within the build graph
                StringBuilder builder = builderWrapper.Instance;
                builder.Append(projectWithPredictions.FullPath.ToString(PathTable));
                builder.Append("|");
                builder.Append(projectWithPredictions.PredictedTargetsToExecute.IsDefaultTargetsAppended);
                builder.Append(string.Join("|", projectWithPredictions.PredictedTargetsToExecute.Targets));

                builder.Append("|");
                builder.Append(string.Join("|", projectWithPredictions.GlobalProperties.Select(kvp => kvp.Key + "|" + kvp.Value)));

                return(PipConstructionUtilities.ComputeSha256(builder.ToString()));
            }
        }
예제 #12
0
        private void SetUntrackedFilesAndDirectories(ProcessBuilder processBuilder)
        {
            // On some machines, the current user and public user desktop.ini are read by Powershell.exe.
            // Ignore accesses to the user profile and Public common user profile.
            processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.UserProfile)));

            if (Engine.TryGetBuildParameter("PUBLIC", m_frontEndName, out string publicDir))
            {
                processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(PathTable, publicDir)));
            }

            PipConstructionUtilities.UntrackUserConfigurableArtifacts(processBuilder, m_resolverSettings);

            // Git accesses should be ignored if .git directory is there
            var gitDirectory = Root.Combine(PathTable, ".git");

            if (Engine.DirectoryExists(gitDirectory))
            {
                processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(gitDirectory));
                processBuilder.AddUntrackedFile(FileArtifact.CreateSourceFile(Root.Combine(PathTable, ".gitattributes")));
                processBuilder.AddUntrackedFile(FileArtifact.CreateSourceFile(Root.Combine(PathTable, ".gitignore")));
            }
        }
예제 #13
0
        private void UntrackFilesAndDirectories(ProcessBuilder processBuilder)
        {
            processBuilder.AddUntrackedWindowsDirectories();
            processBuilder.AddUntrackedProgramDataDirectories();
            processBuilder.AddUntrackedAppDataDirectories();

            if (m_untrackingSettings != null)
            {
                PipConstructionUtilities.UntrackUserConfigurableArtifacts(processBuilder, m_untrackingSettings);
            }

            var programFilesDirectoryArtifact    = DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(m_context.PathTable, Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)));
            var programFilesX86DirectoryArtifact = DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(m_context.PathTable, Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)));

            processBuilder.AddUntrackedDirectoryScope(programFilesDirectoryArtifact);
            processBuilder.AddUntrackedDirectoryScope(programFilesX86DirectoryArtifact);
            processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(m_context.PathTable, @"C:\PROGRA~1\"))); // TODO: This but better
            processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(m_context.PathTable, @"C:\PROGRA~2\"))); // TODO: This but better


            // TODO: This is just here because the cloud build requires manually dropping the necessary executables and libraries, and should be removed
            // when that issue is resolved.
            string toolsDir = m_manuallyDroppedDependenciesPath.Value.ToString(m_context.PathTable);

            processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(AbsolutePath.Create(m_context.PathTable, toolsDir)));

            // Git accesses should be ignored if .git directory is there
            var gitDirectory = m_projectRoot.Combine(m_context.PathTable, ".git");

            if (m_frontEndHost.Engine.DirectoryExists(gitDirectory))
            {
                processBuilder.AddUntrackedDirectoryScope(DirectoryArtifact.CreateWithZeroPartialSealId(gitDirectory));
                processBuilder.AddUntrackedFile(FileArtifact.CreateSourceFile(m_projectRoot.Combine(m_context.PathTable, ".gitattributes")));
                processBuilder.AddUntrackedFile(FileArtifact.CreateSourceFile(m_projectRoot.Combine(m_context.PathTable, ".gitignore")));
            }
        }
예제 #14
0
        private PipConstructionHelper GetPipConstructionHelperForModule(AbsolutePath projectRoot, ModuleDefinition moduleDefinition, QualifierId qualifierId)
        {
            // One and only one AbsolutePath in the specs (corresponding to the build.ninja
            // If this changed, this method would be out of here, as we would need a different PipConstructionHelper
            // for each spec
            Contract.Requires(moduleDefinition.Specs.Count == 1);

            // Get a symbol that is unique for this particular instance
            var          fullSymbol = FullSymbol.Create(m_context.SymbolTable, $"ninja.{PipConstructionUtilities.SanitizeStringForSymbol(moduleDefinition.Descriptor.Name)}"); // TODO: Figure this out, complete
            AbsolutePath pathToSpec = moduleDefinition.Specs.First();

            if (!projectRoot.TryGetRelative(m_context.PathTable, pathToSpec, out var specRelativePath))
            {
                // Issue a warning and continue with Invalid path. PipConstructionHelper will just ignore
                Tracing.Logger.Log.CouldNotComputeRelativePathToSpec(m_context.LoggingContext,
                                                                     Location.FromFile(projectRoot.ToString(m_context.PathTable)),
                                                                     projectRoot.ToString(m_context.PathTable),
                                                                     pathToSpec.ToString(m_context.PathTable));
                specRelativePath = RelativePath.Invalid;
            }

            var pipConstructionHelper = PipConstructionHelper.Create(
                m_context,
                m_frontEndHost.Engine.Layout.ObjectDirectory,
                m_frontEndHost.Engine.Layout.RedirectedDirectory,
                m_frontEndHost.Engine.Layout.TempDirectory,
                m_frontEndHost.PipGraph,
                moduleDefinition.Descriptor.Id,
                moduleDefinition.Descriptor.Name,
                specRelativePath,
                fullSymbol,
                new LocationData(pathToSpec, 0, 0), // TODO: This is the location of the value (that is scheduling pips through this helper) in its corresponding spec.
                                                    // Since we are not exposing any public value yet that represents this symbol, this location is fine.
                qualifierId);

            return(pipConstructionHelper);
        }
예제 #15
0
 /// <summary>
 /// Project-specific user profile folder
 /// </summary>
 internal static AbsolutePath UserProfile(JavaScriptProject project, PathTable pathTable) => project.TempFolder
 .Combine(pathTable, "USERPROFILE")
 .Combine(pathTable, PipConstructionUtilities.SanitizeStringForSymbol(project.ScriptCommandName));