Ejemplo n.º 1
0
        public void ValidateAllowlistCaseSensitivity()
        {
            FileArtifact exe = GetOsShell();
            PathAtom     exeNameUpperCase = PathAtom.Create(Context.StringTable, exe.Path.GetName(Context.PathTable).ToString(Context.StringTable).ToUpper());
            var          exeLinkUnion     = new DiscriminatingUnion <FileArtifact, PathAtom>(exeNameUpperCase);

            Configuration.CacheableFileAccessAllowlist.Add(new Configuration.Mutable.FileAccessAllowlistEntry()
            {
                ToolPath = exeLinkUnion, PathRegex = ".*"
            });

            FileArtifact output = CreateOutputFileArtifact();

            CreateAndScheduleOsShellPip(exe, output);

            if (OperatingSystemHelper.IsPathComparisonCaseSensitive)
            {
                // This should fail on an OS that has case sensitive paths because we converted the process name to uppercase
                RunScheduler().AssertFailure();
                AssertLogContains(true, $"W  {output.Path.ToString(Context.PathTable)}");
                AssertErrorEventLogged(LogEventId.FileMonitoringError, 1);
                AssertWarningEventLogged(LogEventId.ProcessNotStoredToCacheDueToFileMonitoringViolations, 1);
            }
            else
            {
                RunScheduler().AssertSuccess();
                XAssert.AreEqual("hi", File.ReadAllText(ArtifactToString(output)).Trim().Trim('\''));
            }
        }
Ejemplo n.º 2
0
        private static DiscriminatingUnion <AbsolutePath, IInlineModuleDefinition> RemapModule(
            DiscriminatingUnion <AbsolutePath, IInlineModuleDefinition> fileOrInlineModule,
            PathRemapper pathRemapper)
        {
            var fileOrInlineModuleValue = fileOrInlineModule?.GetValue();

            if (fileOrInlineModuleValue == null)
            {
                return(null);
            }

            if (fileOrInlineModuleValue is AbsolutePath path)
            {
                return(new DiscriminatingUnion <AbsolutePath, IInlineModuleDefinition>(pathRemapper.Remap(path)));
            }

            var inlineModuleDefinition = (IInlineModuleDefinition)fileOrInlineModuleValue;

            var remappedInlineModuleDefinition = new InlineModuleDefinition
            {
                ModuleName = inlineModuleDefinition.ModuleName,
                Projects   = inlineModuleDefinition.Projects?.Select(project => pathRemapper.Remap(project)).ToList()
            };

            return(new DiscriminatingUnion <AbsolutePath, IInlineModuleDefinition>(remappedInlineModuleDefinition));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Returns all projects that the actual selector in the discriminating union specifies
        /// </summary>
        public bool TryGetMatches(DiscriminatingUnion <string, IJavaScriptProjectSimpleSelector, IJavaScriptProjectRegexSelector> selector, out IReadOnlyCollection <JavaScriptProject> matches, out string failure)
        {
            failure = string.Empty;
            switch (selector.GetValue())
            {
            case string s:
                matches = GetMatches(s);
                return(true);

            case IJavaScriptProjectSimpleSelector simpleSelector:
                matches = GetMatches(simpleSelector);
                return(true);

            case IJavaScriptProjectRegexSelector regexSelector:
                try
                {
                    matches = GetMatches(regexSelector);
                }
                catch (ArgumentException e)
                {
                    matches = CollectionUtilities.EmptyArray <JavaScriptProject>();
                    failure = e.Message;
                    return(false);
                }

                return(true);

            default:
                Contract.Assert(false, $"Unexpected type {selector.GetValue().GetType()}");
                matches = CollectionUtilities.EmptyArray <JavaScriptProject>();
                return(false);
            }
        }
        /// <summary>
        /// Construct a new allowlist entry that will match based on tool and path.
        /// </summary>
        /// <param name="executable">The exact full path or the executable name to the tool that does the bad access.</param>
        /// <param name="pathRegex">The ECMAScript regex pattern that will be used as the basis for the match.</param>
        /// <param name="allowsCaching">
        /// Whether this allowlist rule should be interpreted to allow caching of a pip that matches
        /// it.
        /// </param>
        /// <param name="name">Name of the allowlist entry. Defaults to 'Unnamed' if null/empty.</param>
        public ExecutablePathAllowlistEntry(DiscriminatingUnion <AbsolutePath, PathAtom> executable, SerializableRegex pathRegex, bool allowsCaching, string name)
            : base(pathRegex, allowsCaching, name)
        {
            Contract.RequiresNotNull(executable);
            Contract.Requires(pathRegex != null);

            Executable = executable;
        }
Ejemplo n.º 5
0
        public void AllowlistOnSpawnProcess(bool includeExecutableLink)
        {
            FileArtifact exeLink      = CreateOsShellExecutableSymbolicLink();
            var          exeLinkUnion = new DiscriminatingUnion <FileArtifact, PathAtom>(exeLink);

            Configuration.CacheableFileAccessAllowlist.Add(new Configuration.Mutable.FileAccessAllowlistEntry()
            {
                ToolPath = exeLinkUnion, PathRegex = ".*"
            });

            FileArtifact output = CreateOutputFileArtifact();

            var builder = CreatePipBuilder(new[]
            {
                Operation.SpawnExe(
                    Context.PathTable,
                    exeLink,
                    string.Format(OperatingSystemHelper.IsUnixOS ? "-c \"echo 'hi' > {0}\"" : "/d /c echo 'hi' > {0}", output.Path.ToString(Context.PathTable))),
            });

            builder.AddOutputFile(output.Path);

            if (includeExecutableLink)
            {
                builder.AddInputFile(exeLink);
            }

            foreach (var dep in CmdHelper.GetCmdDependencies(Context.PathTable))
            {
                builder.AddUntrackedFile(dep);
            }

            foreach (var dep in CmdHelper.GetCmdDependencyScopes(Context.PathTable))
            {
                builder.AddUntrackedDirectoryScope(dep);
            }

            SchedulePipBuilder(builder);

            if (includeExecutableLink)
            {
                RunScheduler().AssertSuccess();
                XAssert.AreEqual("hi", File.ReadAllText(ArtifactToString(output)).Trim().Trim('\''));
            }
            else
            {
                RunScheduler().AssertFailure();

                // DFA on exeLink because it is not specified as input.
                // Although there's a cacheable allowlist entry for exeLink, that entry only holds for file accessed by exeLink.
                // In this case, exeLink is accessed by the test process, so there's a read operation by the test process on exeLink, hence DFA.
                AssertErrorEventLogged(LogEventId.FileMonitoringError, 1);
                AssertLogContains(false, $"R  {exeLink.Path.ToString(Context.PathTable)}");

                AssertWarningEventLogged(LogEventId.ProcessNotStoredToCacheDueToFileMonitoringViolations, 1);
            }
        }
Ejemplo n.º 6
0
        private bool ValidateProjectSelector(DiscriminatingUnion <string, IJavaScriptProjectSimpleSelector, IJavaScriptProjectRegexSelector> selector, string pathToFile, string provenance)
        {
            object projectValue = selector.GetValue();

            switch (projectValue)
            {
            case string packageName:
            {
                if (string.IsNullOrEmpty(packageName))
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Package name for {provenance} must be defined.");
                    return(false);
                }

                break;
            }

            case IJavaScriptProjectSimpleSelector simpleSelector:
            {
                if (string.IsNullOrEmpty(simpleSelector.PackageName))
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Package name for {provenance} must be defined.");
                    return(false);
                }

                if (simpleSelector.Commands == null)
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Commands for {provenance} must be defined.");
                    return(false);
                }

                foreach (var command in simpleSelector.Commands)
                {
                    if (string.IsNullOrEmpty(command))
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Command name for {provenance} must be defined.");
                        return(false);
                    }
                }
                break;
            }

            case IJavaScriptProjectRegexSelector regexSelector:
            {
                if (string.IsNullOrEmpty(regexSelector.PackageNameRegex))
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Package name regular expression for {provenance} must be defined.");
                    return(false);
                }

                break;
            }
            }

            return(true);
        }
Ejemplo n.º 7
0
 /// <nodoc/>
 public CustomJavaScriptResolverSettings(ICustomJavaScriptResolverSettings resolverSettings, PathRemapper pathRemapper) : base(resolverSettings, pathRemapper)
 {
     if (resolverSettings.CustomProjectGraph?.GetValue() is AbsolutePath absolutePath)
     {
         CustomProjectGraph = new DiscriminatingUnion <AbsolutePath, IReadOnlyDictionary <string, IJavaScriptCustomProjectGraphNode> >(pathRemapper.Remap(absolutePath));
     }
     else
     {
         CustomProjectGraph = resolverSettings.CustomProjectGraph;
     }
 }
Ejemplo n.º 8
0
        public void ValidateAllowlistWithFullPathOnSpawnedProcess()
        {
            FileArtifact exe          = GetOsShell();
            var          exeLinkUnion = new DiscriminatingUnion <FileArtifact, PathAtom>(exe);

            Configuration.CacheableFileAccessAllowlist.Add(new Configuration.Mutable.FileAccessAllowlistEntry()
            {
                ToolPath = exeLinkUnion, PathRegex = ".*"
            });

            FileArtifact output = CreateOutputFileArtifact();

            CreateAndScheduleOsShellPip(exe, output);

            RunScheduler().AssertSuccess();
            XAssert.AreEqual("hi", File.ReadAllText(ArtifactToString(output)).Trim().Trim('\''));
        }
Ejemplo n.º 9
0
        public void EnvironmentIsHonored()
        {
            var one = new DiscriminatingUnion <string, UnitValue>();

            one.SetValue("1");

            var project  = CreateProjectWithPredictions("A.proj");
            var testProj = Start(new MsBuildResolverSettings {
                Environment = new Dictionary <string, DiscriminatingUnion <string, UnitValue> > {
                    ["Test"] = one
                }
            })
                           .Add(project)
                           .ScheduleAll()
                           .AssertSuccess()
                           .RetrieveSuccessfulProcess(project);

            var testEnvironmentVariable = testProj.EnvironmentVariables.First(e => e.Name.ToString(PathTable.StringTable).Equals("Test"));

            Assert.Equal("1", testEnvironmentVariable.Value.ToString(PathTable));
        }
Ejemplo n.º 10
0
        /// <nodoc />
        public DiscriminatingUnion <FileArtifact, PathAtom> Remap(DiscriminatingUnion <FileArtifact, PathAtom> fileUnion)
        {
            DiscriminatingUnion <FileArtifact, PathAtom> remappedPath = null;

            if (fileUnion != null)
            {
                var fileValue = fileUnion.GetValue();
                remappedPath = new DiscriminatingUnion <FileArtifact, PathAtom>();

                if (fileValue is FileArtifact file)
                {
                    remappedPath.SetValue(Remap(file));
                }
                else if (fileValue is PathAtom pathAtom)
                {
                    remappedPath.SetValue(Remap(pathAtom));
                }
            }

            return(remappedPath);
        }
Ejemplo n.º 11
0
        /// <nodoc />
        public DiscriminatingUnion <FileArtifact, IReadOnlyList <DirectoryArtifact> > Remap(DiscriminatingUnion <FileArtifact, IReadOnlyList <DirectoryArtifact> > fileUnion)
        {
            DiscriminatingUnion <FileArtifact, IReadOnlyList <DirectoryArtifact> > remappedPath = null;

            if (fileUnion != null)
            {
                var fileValue = fileUnion.GetValue();
                remappedPath = new DiscriminatingUnion <FileArtifact, IReadOnlyList <DirectoryArtifact> >();

                if (fileValue is FileArtifact file)
                {
                    remappedPath.SetValue(Remap(file));
                }
                else if (fileValue is IReadOnlyList <DirectoryArtifact> searchDirectories)
                {
                    remappedPath.SetValue(Remap(searchDirectories));
                }
            }

            return(remappedPath);
        }
Ejemplo n.º 12
0
        public void ValidateOverlappingAllowlistToolPaths()
        {
            FileArtifact exe             = GetOsShell();
            var          exeLinkFullPath = new DiscriminatingUnion <FileArtifact, PathAtom>(exe);
            var          exeLinkNameOnly = new DiscriminatingUnion <FileArtifact, PathAtom>(exe.Path.GetName(Context.PathTable));

            Configuration.CacheableFileAccessAllowlist.Add(new Configuration.Mutable.FileAccessAllowlistEntry()
            {
                ToolPath = exeLinkFullPath, PathRegex = ".*"
            });
            Configuration.CacheableFileAccessAllowlist.Add(new Configuration.Mutable.FileAccessAllowlistEntry()
            {
                ToolPath = exeLinkNameOnly, PathRegex = ".*"
            });

            FileArtifact output = CreateOutputFileArtifact();

            CreateAndScheduleOsShellPip(exe, output);

            RunScheduler().AssertSuccess();
            XAssert.AreEqual("hi", File.ReadAllText(ArtifactToString(output)).Trim().Trim('\''));
        }
Ejemplo n.º 13
0
        private static DirectoryArtifact ResolveAbsoluteOrRelativeDirectory(PathTable pathTable, DiscriminatingUnion <DirectoryArtifact, RelativePath> absoluteOrRelativeUnion, AbsolutePath root)
        {
            var absoluteOrRelative = absoluteOrRelativeUnion.GetValue();

            if (absoluteOrRelative is DirectoryArtifact directory)
            {
                return(directory);
            }

            var relative = (RelativePath)absoluteOrRelative;

            if (!relative.IsValid)
            {
                return(DirectoryArtifact.Invalid);
            }

            return(DirectoryArtifact.CreateWithZeroPartialSealId(root.Combine(pathTable, relative)));
        }
        public async Task TestSerialization()
        {
            var context = BuildXLContext.CreateInstanceForTesting();

            var pathTable   = context.PathTable;
            var stringTable = context.StringTable;
            var symbolTable = new SymbolTable(pathTable.StringTable);
            var allowlist   = new FileAccessAllowlist(context);

            //Allowlist with full paths
            var path1            = new DiscriminatingUnion <AbsolutePath, PathAtom>(AbsolutePath.Create(pathTable, @"\\fakePath\foo.txt"));
            var path2            = new DiscriminatingUnion <AbsolutePath, PathAtom>(AbsolutePath.Create(pathTable, @"\\fakePath\bar.txt"));
            var regex1           = new SerializableRegex(@"dir\foo.txt");
            var executableEntry1 = new ExecutablePathAllowlistEntry(
                path1, regex1, true, "entry1");
            var executableEntry2 = new ExecutablePathAllowlistEntry(
                path2, new SerializableRegex("bar"), false, "entry2");

            allowlist.Add(executableEntry1);
            allowlist.Add(executableEntry2);

            // Allowlist with executable names only
            var path3            = new DiscriminatingUnion <AbsolutePath, PathAtom>(PathAtom.Create(stringTable, "alice.txt"));
            var path4            = new DiscriminatingUnion <AbsolutePath, PathAtom>(PathAtom.Create(stringTable, "bob.txt"));
            var regex3           = new SerializableRegex(@"dir\alice.txt");
            var executableEntry3 = new ExecutablePathAllowlistEntry(
                path3, regex3, true, "entry5");
            var executableEntry4 = new ExecutablePathAllowlistEntry(
                path4, new SerializableRegex("bob"), false, "entry6");

            allowlist.Add(executableEntry3);
            allowlist.Add(executableEntry4);

            var symbol1    = FullSymbol.Create(symbolTable, "symbol1");
            var valueEntry = new ValuePathFileAccessAllowlistEntry(
                symbol1, new SerializableRegex("symbol1"), false, null);

            var symbol2     = FullSymbol.Create(symbolTable, "symbol2");
            var valueEntry2 = new ValuePathFileAccessAllowlistEntry(
                symbol2, new SerializableRegex("symbol2"), false, "entry4");

            allowlist.Add(valueEntry);
            allowlist.Add(valueEntry2);

            XAssert.AreEqual(4, allowlist.UncacheableEntryCount);
            XAssert.AreEqual(2, allowlist.CacheableEntryCount);
            XAssert.AreEqual("Unnamed", valueEntry.Name);

            using (var ms = new MemoryStream())
            {
                BuildXLWriter writer = new BuildXLWriter(true, ms, true, true);
                allowlist.Serialize(writer);

                ms.Position = 0;
                BuildXLReader reader       = new BuildXLReader(true, ms, true);
                var           deserialized = await FileAccessAllowlist.DeserializeAsync(reader, Task.FromResult <PipExecutionContext>(context));

                var path1Absolute = (AbsolutePath)path1.GetValue();
                var path2Absolute = (AbsolutePath)path2.GetValue();
                var path3Atom     = ((PathAtom)path3.GetValue()).StringId;
                var path4Atom     = ((PathAtom)path4.GetValue()).StringId;

                XAssert.AreEqual(2, deserialized.ExecutablePathEntries.Count);
                XAssert.AreEqual(1, deserialized.ExecutablePathEntries[path1Absolute].Count);
                XAssert.AreEqual(true, deserialized.ExecutablePathEntries[path1Absolute][0].AllowsCaching);
                XAssert.AreEqual(regex1.ToString(), deserialized.ExecutablePathEntries[path1Absolute][0].PathRegex.ToString());
                XAssert.AreEqual(executableEntry1.Name, deserialized.ExecutablePathEntries[path1Absolute][0].Name);
                XAssert.AreEqual(executableEntry2.Name, deserialized.ExecutablePathEntries[path2Absolute][0].Name);

                XAssert.AreEqual(2, deserialized.ToolExecutableNameEntries.Count);
                XAssert.AreEqual(1, deserialized.ToolExecutableNameEntries[path3Atom].Count);
                XAssert.AreEqual(true, deserialized.ToolExecutableNameEntries[path3Atom][0].AllowsCaching);
                XAssert.AreEqual(regex3.ToString(), deserialized.ToolExecutableNameEntries[path3Atom][0].PathRegex.ToString());
                XAssert.AreEqual(executableEntry3.Name, deserialized.ToolExecutableNameEntries[path3Atom][0].Name);
                XAssert.AreEqual(executableEntry4.Name, deserialized.ToolExecutableNameEntries[path4Atom][0].Name);

                XAssert.AreEqual(2, deserialized.ValuePathEntries.Count);
                XAssert.AreEqual(1, deserialized.ValuePathEntries[symbol1].Count);
                XAssert.AreEqual(false, deserialized.ValuePathEntries[symbol1][0].AllowsCaching);
                XAssert.AreEqual(valueEntry.Name, deserialized.ValuePathEntries[symbol1][0].Name);
                XAssert.AreEqual(valueEntry2.Name, deserialized.ValuePathEntries[symbol2][0].Name);

                XAssert.AreEqual(4, deserialized.UncacheableEntryCount);
                XAssert.AreEqual(2, deserialized.CacheableEntryCount);
            }
        }
Ejemplo n.º 15
0
        /// <nodoc/>
        protected virtual bool ValidateResolverSettings(TResolverSettings resolverSettings)
        {
            var pathToFile = resolverSettings.File.ToString(Context.PathTable);

            if (!resolverSettings.Root.IsValid)
            {
                Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), "The root must be specified.");
                return(false);
            }

            if (string.IsNullOrEmpty(resolverSettings.ModuleName))
            {
                Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), "The module name must not be empty.");
                return(false);
            }

            if (resolverSettings.CustomCommands != null)
            {
                var commandNames = new HashSet <string>();
                foreach (var customCommand in resolverSettings.CustomCommands)
                {
                    if (string.IsNullOrEmpty(customCommand.Command))
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), "A non-empty custom command name must be defined.");
                        return(false);
                    }

                    if (!commandNames.Add(customCommand.Command))
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Duplicated custom command name '{customCommand.Command}'.");
                        return(false);
                    }

                    if (customCommand.ExtraArguments == null)
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Extra arguments for custom command '{customCommand.Command}' must be defined.");
                        return(false);
                    }
                }
            }

            if (resolverSettings.Exports != null)
            {
                var symbolNames = new HashSet <FullSymbol>();
                foreach (var javaScriptExport in resolverSettings.Exports)
                {
                    if (!javaScriptExport.SymbolName.IsValid)
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Symbol name is undefined.");
                        return(false);
                    }

                    if (!symbolNames.Add(javaScriptExport.SymbolName))
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Duplicate symbol name '{javaScriptExport.SymbolName.ToString(Context.SymbolTable)}'.");
                        return(false);
                    }

                    // Each specified project must be non-empty
                    foreach (var selector in javaScriptExport.Content)
                    {
                        if (!ValidateProjectSelector(selector, pathToFile, $"JavaScript export '{javaScriptExport.SymbolName.ToString(Context.SymbolTable)}'"))
                        {
                            return(false);
                        }
                    }
                }
            }

            if (resolverSettings.CustomScheduling != null)
            {
                if (string.IsNullOrEmpty(resolverSettings.CustomScheduling.Module))
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Module name for the custom scheduling entry must be defined.");
                    return(false);
                }

                if (string.IsNullOrEmpty(resolverSettings.CustomScheduling.SchedulingFunction))
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Custom scheduling function name must be defined.");
                    return(false);
                }

                if (FullSymbol.TryCreate(Context.SymbolTable, resolverSettings.CustomScheduling.SchedulingFunction, out _, out _) != FullSymbol.ParseResult.Success)
                {
                    Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Custom scheduling function name is not a valid dotted identifier.");
                    return(false);
                }
            }

            if (resolverSettings.AdditionalDependencies != null)
            {
                foreach (var additionalDependency in resolverSettings.AdditionalDependencies)
                {
                    if (additionalDependency.Dependents == null)
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Dependents is undefined.");
                        return(false);
                    }

                    if (additionalDependency.Dependencies == null)
                    {
                        Tracing.Logger.Log.InvalidResolverSettings(Context.LoggingContext, Location.FromFile(pathToFile), $"Dependencies is undefined.");
                        return(false);
                    }

                    foreach (var dependent in additionalDependency.Dependents)
                    {
                        if (!ValidateProjectSelector(dependent, pathToFile, "JavaScript dependent"))
                        {
                            return(false);
                        }
                    }

                    foreach (var dependency in additionalDependency.Dependencies.Where(dependency => dependency.GetValue() is not ILazyEval))
                    {
                        var javaScriptProjectSelector = new DiscriminatingUnion <string, IJavaScriptProjectSimpleSelector, IJavaScriptProjectRegexSelector>();
                        javaScriptProjectSelector.TrySetValue(dependency.GetValue());
                        if (!ValidateProjectSelector(javaScriptProjectSelector, pathToFile, "JavaScript dependency"))
                        {
                            return(false);
                        }
                    }
                }
            }

            return(true);
        }
Ejemplo n.º 16
0
        private static bool ComputeCommands(
            LoggingContext context,
            Location location,
            IReadOnlyList<DiscriminatingUnion<string, IJavaScriptCommand>> commands,
            out IReadOnlyDictionary<string, IReadOnlyList<IJavaScriptCommandDependency>> resultingCommands)
        {
            if (commands == null)
            {
                // If not defined, the default is ["build"]
                commands = new[] { new DiscriminatingUnion<string, IJavaScriptCommand>("build") };
            }

            var computedCommands = new Dictionary<string, IReadOnlyList<IJavaScriptCommandDependency>>(commands.Count);
            resultingCommands = computedCommands;

            for (int i = 0; i < commands.Count; i++)
            {
                DiscriminatingUnion<string, IJavaScriptCommand> command = commands[i];
                string commandName = command.GetCommandName();

                if (string.IsNullOrEmpty(commandName))
                {
                    Tracing.Logger.Log.JavaScriptCommandIsEmpty(context, location);
                    return false;
                }

                if (computedCommands.ContainsKey(commandName))
                {
                    Tracing.Logger.Log.JavaScriptCommandIsDuplicated(context, location, commandName);
                    return false;
                }

                if (command.GetValue() is string simpleCommand)
                {
                    // A simple string command first on the list means depending on the same command of all its dependencies.
                    // Canonical example: 'build'
                    if (i == 0)
                    {
                        computedCommands.Add(
                            simpleCommand,
                            new[] { new JavaScriptCommandDependency { Command = simpleCommand, Kind = JavaScriptCommandDependency.Package } });
                    }
                    else
                    {
                        // A simple string command that is not first in the list means depending on the immediate predecesor in the list
                        // Canonical example: 'build', 'test'
                        computedCommands.Add(
                            simpleCommand,
                            new[] { new JavaScriptCommandDependency { Command = commands[i - 1].GetCommandName(), Kind = JavaScriptCommandDependency.Local } });
                    }
                }
                else
                {
                    // Otherwise if a full fledge command is specified, then we honor it as is
                    var javaScriptCommand = (IJavaScriptCommand)command.GetValue();
                    computedCommands.Add(commandName, javaScriptCommand.DependsOn);
                }
            }

            return true;
        }
Ejemplo n.º 17
0
        private static bool ComputeCommands(
            LoggingContext context,
            Location location,
            IReadOnlyList <DiscriminatingUnion <string, IJavaScriptCommand, IJavaScriptCommandGroupWithDependencies, IJavaScriptCommandGroup> > commands,
            out IReadOnlyDictionary <string, IReadOnlyList <IJavaScriptCommandDependency> > resultingCommands,
            out IReadOnlyDictionary <string, IReadOnlyList <string> > resultingCommandGroups)
        {
            if (commands == null)
            {
                // If not defined, the default is ["build"]
                commands = new[] { new DiscriminatingUnion <string, IJavaScriptCommand, IJavaScriptCommandGroupWithDependencies, IJavaScriptCommandGroup>("build") };
            }

            var computedCommands = new Dictionary <string, IReadOnlyList <IJavaScriptCommandDependency> >(commands.Count);

            resultingCommands = computedCommands;
            var commandGroups = new Dictionary <string, IReadOnlyList <string> >();

            resultingCommandGroups = commandGroups;

            for (int i = 0; i < commands.Count; i++)
            {
                DiscriminatingUnion <string, IJavaScriptCommand, IJavaScriptCommandGroupWithDependencies, IJavaScriptCommandGroup> command = commands[i];
                string commandName = command.GetCommandName();

                if (string.IsNullOrEmpty(commandName))
                {
                    Tracing.Logger.Log.JavaScriptCommandIsEmpty(context, location);
                    return(false);
                }

                if (computedCommands.ContainsKey(commandName))
                {
                    Tracing.Logger.Log.JavaScriptCommandIsDuplicated(context, location, commandName);
                    return(false);
                }

                object commandValue = command.GetValue();
                if (commandValue is string simpleCommand)
                {
                    // A simple string command first on the list means depending on the same command of all its dependencies.
                    // Canonical example: 'build'
                    if (i == 0)
                    {
                        computedCommands.Add(
                            simpleCommand,
                            new[] { new JavaScriptCommandDependency {
                                        Command = simpleCommand, Kind = JavaScriptCommandDependency.Package
                                    } });
                    }
                    else
                    {
                        // A simple string command that is not first in the list means depending on the immediate predecesor in the list
                        // Canonical example: 'build', 'test'
                        computedCommands.Add(
                            simpleCommand,
                            new[] { new JavaScriptCommandDependency {
                                        Command = commands[i - 1].GetCommandName(), Kind = JavaScriptCommandDependency.Local
                                    } });
                    }
                }
                else
                {
                    // Otherwise if a full fledge command is specified, we honor it as is

                    // The command may specify dependencies, in which case we add it to the map
                    // of computed commands. Cases like the Lage resolver explicitly don't expose
                    // commands with dependencies since it is Lage the one that defines them
                    if (commandValue is IJavaScriptCommandWithDependencies commandWithDependencies)
                    {
                        computedCommands.Add(commandName, commandWithDependencies.DependsOn);
                    }

                    // Deal with the case of group commands
                    if (commandValue is IJavaScriptCommandGroup commandGroup)
                    {
                        var emptyCommands = commandGroup.Commands.Where(command => string.IsNullOrEmpty(command));
                        if (emptyCommands.Any())
                        {
                            Tracing.Logger.Log.JavaScriptCommandIsEmpty(context, location);
                            return(false);
                        }

                        // Check that command members cannot be groups commands as well
                        var dup = commandGroup.Commands.FirstOrDefault(command => computedCommands.ContainsKey(command));
                        if (dup != default)
                        {
                            Tracing.Logger.Log.JavaScriptCommandGroupCanOnlyContainRegularCommands(context, location, commandGroup.CommandName, dup);
                            return(false);
                        }

                        commandGroups.Add(commandGroup.CommandName, commandGroup.Commands);
                    }
                }
            }

            return(true);
        }