Пример #1
0
        /// <summary>
        /// Attempts to resolve assembly references that were specified by the user.
        /// </summary>
        /// <param name="log">A <see cref="TaskLoggingHelper"/> used for logging.</param>
        /// <param name="taskInfo">A <see cref="RoslynCodeTaskFactoryTaskInfo"/> object containing details about the task.</param>
        /// <param name="items">Receives the list of full paths to resolved assemblies.</param>
        /// <returns><code>true</code> if all assemblies could be resolved, otherwise <code>false</code>.</returns>
        /// <remarks>The user can specify a short name like My.Assembly or My.Assembly.dll.  In this case we'll
        /// attempt to look it up in the directory containing our reference assemblies.  They can also specify a
        /// full path and we'll do no resolution.  At this time, these are the only two resolution mechanisms.
        /// Perhaps in the future this could be more powerful by using NuGet to resolve assemblies but we think
        /// that is too complicated for a simple in-line task.  If users have more complex requirements, they
        /// can compile their own task library.</remarks>
        internal static bool TryResolveAssemblyReferences(TaskLoggingHelper log, RoslynCodeTaskFactoryTaskInfo taskInfo, out ITaskItem[] items)
        {
            // Store the list of resolved assemblies because a user can specify a short name or a full path
            ISet <string> resolvedAssemblyReferences = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            // Keeps track if there were one or more unresolved assemblies
            bool hasInvalidReference = false;

            // Start with the user specified references and include all of the default references that are language agnostic
            IEnumerable <string> references = taskInfo.References.Union(DefaultReferences[String.Empty]);

            if (DefaultReferences.ContainsKey(taskInfo.CodeLanguage))
            {
                // Append default references for the specific language
                references = references.Union(DefaultReferences[taskInfo.CodeLanguage]);
            }

            // Loop through the user specified references as well as the default references
            foreach (string reference in references)
            {
                // The user specified a full path to an assembly, so there is no need to resolve
                if (FileSystems.Default.FileExists(reference))
                {
                    // The path could be relative like ..\Assembly.dll so we need to get the full path
                    resolvedAssemblyReferences.Add(Path.GetFullPath(reference));
                    continue;
                }

                // Attempt to "resolve" the assembly by getting a full path to our distributed reference assemblies
                string assemblyFileName = reference.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || reference.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)
                    ? reference
                    : $"{reference}.dll";

                string resolvedDir = new[]
                {
                    Path.Combine(ThisAssemblyDirectoryLazy.Value, ReferenceAssemblyDirectoryName),
                    ThisAssemblyDirectoryLazy.Value,
                }
                .Concat(MonoLibDirs)
                .FirstOrDefault(p => File.Exists(Path.Combine(p, assemblyFileName)));

                if (resolvedDir != null)
                {
                    resolvedAssemblyReferences.Add(Path.Combine(resolvedDir, assemblyFileName));
                    continue;
                }

                // Could not resolve the assembly.  We currently don't support looking things up the GAC so that in-line task
                // assemblies are portable across platforms
                log.LogErrorWithCodeFromResources("CodeTaskFactory.CouldNotFindReferenceAssembly", reference);

                hasInvalidReference = true;
            }

            // Transform the list of resolved assemblies to TaskItems if they were all resolved
            items = hasInvalidReference ? null : resolvedAssemblyReferences.Select(i => (ITaskItem) new TaskItem(i)).ToArray();

            return(!hasInvalidReference);
        }
Пример #2
0
        /// <summary>
        /// Attempts to compile the current source code and load the assembly into memory.
        /// </summary>
        /// <param name="buildEngine">An <see cref="IBuildEngine"/> to use give to the compiler task so that messages can be logged.</param>
        /// <param name="taskInfo">A <see cref="RoslynCodeTaskFactoryTaskInfo"/> object containing details about the task.</param>
        /// <param name="assembly">The <see cref="Assembly"/> if the source code be compiled and loaded, otherwise <code>null</code>.</param>
        /// <returns><code>true</code> if the source code could be compiled and loaded, otherwise <code>null</code>.</returns>
        private bool TryCompileInMemoryAssembly(IBuildEngine buildEngine, RoslynCodeTaskFactoryTaskInfo taskInfo, out Assembly assembly)
        {
            // First attempt to get a compiled assembly from the cache
            if (CompiledAssemblyCache.TryGetValue(taskInfo, out assembly))
            {
                return(true);
            }

            if (!TryResolveAssemblyReferences(_log, taskInfo, out ITaskItem[] references))
Пример #3
0
        ///  <summary>
        ///  Parses and validates the body of the &lt;UsingTask /&gt;.
        ///  </summary>
        ///  <param name="log">A <see cref="TaskLoggingHelper"/> used to log events during parsing.</param>
        ///  <param name="taskName">The name of the task.</param>
        ///  <param name="taskBody">The raw inner XML string of the &lt;UsingTask />&gt; to parse and validate.</param>
        /// <param name="parameters">An <see cref="ICollection{TaskPropertyInfo}"/> containing parameters for the task.</param>
        /// <param name="taskInfo">A <see cref="RoslynCodeTaskFactoryTaskInfo"/> object that receives the details of the parsed task.</param>
        /// <returns><code>true</code> if the task body was successfully parsed, otherwise <code>false</code>.</returns>
        ///  <remarks>
        ///  The <paramref name="taskBody"/> will look like this:
        ///  <![CDATA[
        ///
        ///    <Using Namespace="Namespace" />
        ///    <Reference Include="AssemblyName|AssemblyPath" />
        ///    <Code Type="Fragment|Method|Class" Language="cs|vb" Source="Path">
        ///      // Source code
        ///    </Code>
        ///
        ///  ]]>
        ///  </remarks>
        internal static bool TryLoadTaskBody(TaskLoggingHelper log, string taskName, string taskBody, ICollection <TaskPropertyInfo> parameters, out RoslynCodeTaskFactoryTaskInfo taskInfo)
        {
            taskInfo = new RoslynCodeTaskFactoryTaskInfo
            {
                CodeLanguage = "CS",
                CodeType     = RoslynCodeTaskFactoryCodeType.Fragment,
                Name         = taskName,
            };

            XDocument document;

            try
            {
                // For legacy reasons, the inner XML of the <UsingTask /> has no document element.  So we have to add a top-level
                // element around it so it can be parsed.
                document = XDocument.Parse($"<Task>{taskBody}</Task>");
            }
            catch (Exception e)
            {
                log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidTaskXml", e.Message);
                return(false);
            }

            if (document.Root == null)
            {
                log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidTaskXml", String.Empty);
                return(false);
            }

            XElement codeElement = null;

            // Loop through the children, ignoring ones we don't care about, parsing valid ones, and logging an error if we
            // encounter any element that is not recognized.
            foreach (XNode node in document.Root.Nodes()
                     .Where(i => i.NodeType != XmlNodeType.Comment && i.NodeType != XmlNodeType.Whitespace))
            {
                switch (node.NodeType)
                {
                case XmlNodeType.Element:
                    XElement child = (XElement)node;

                    // Parse known elements and go to the default case if its an unknown element
                    if (child.Name.LocalName.Equals("Code"))
                    {
                        if (codeElement != null)
                        {
                            // Only one <Code /> element is allowed.
                            log.LogErrorWithCodeFromResources("CodeTaskFactory.MultipleCodeNodes");
                            return(false);
                        }

                        codeElement = child;
                    }
                    else if (child.Name.LocalName.Equals("Reference"))
                    {
                        XAttribute includeAttribute = child.Attributes().FirstOrDefault(i => i.Name.LocalName.Equals("Include"));

                        if (String.IsNullOrWhiteSpace(includeAttribute?.Value))
                        {
                            // A <Reference Include="" /> is not allowed.
                            log.LogErrorWithCodeFromResources("CodeTaskFactory.AttributeEmptyWithElement", "Include", "Reference");
                            return(false);
                        }

                        // Store the reference in the list
                        taskInfo.References.Add(includeAttribute.Value.Trim());
                    }
                    else if (child.Name.LocalName.Equals("Using"))
                    {
                        XAttribute namespaceAttribute = child.Attributes().FirstOrDefault(i => i.Name.LocalName.Equals("Namespace"));

                        if (String.IsNullOrWhiteSpace(namespaceAttribute?.Value))
                        {
                            // A <Using Namespace="" /> is not allowed
                            log.LogErrorWithCodeFromResources("CodeTaskFactory.AttributeEmptyWithElement", "Namespace", "Using");
                            return(false);
                        }

                        // Store the using in the list
                        taskInfo.Namespaces.Add(namespaceAttribute.Value.Trim());
                    }
                    else
                    {
                        log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidElementLocation",
                                                          child.Name.LocalName,
                                                          document.Root.Name.LocalName);
                        return(false);
                    }

                    break;

                default:
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidElementLocation",
                                                      node.NodeType,
                                                      document.Root.Name.LocalName);
                    return(false);
                }
            }

            if (codeElement == null)
            {
                // <Code /> element is required so if we didn't find it then we need to error
                log.LogErrorWithCodeFromResources("CodeTaskFactory.CodeElementIsMissing", taskName);
                return(false);
            }

            // Copies the source code from the inner text of the <Code /> element.  This might be override later if the user specified
            // a file instead.
            taskInfo.SourceCode = codeElement.Value;

            // Parse the attributes of the <Code /> element
            XAttribute languageAttribute = null;
            XAttribute sourceAttribute   = null;
            XAttribute typeAttribute     = null;

            // TODO: Unit test for this logic and the error message
            foreach (XAttribute attribute in codeElement.Attributes().Where(i => !i.IsNamespaceDeclaration))
            {
                switch (attribute.Name.LocalName)
                {
                case "Language":
                    languageAttribute = attribute;
                    break;

                case "Source":
                    sourceAttribute = attribute;
                    break;

                case "Type":
                    typeAttribute = attribute;
                    break;

                default:
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidCodeElementAttribute",
                                                      attribute.Name.LocalName);
                    return(false);
                }
            }

            if (sourceAttribute != null)
            {
                if (String.IsNullOrWhiteSpace(sourceAttribute.Value))
                {
                    // A <Code Source="" /> is not allowed
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.AttributeEmptyWithElement", "Source", "Code");
                    return(false);
                }

                // Instead of using the inner text of the <Code /> element, read the specified file as source code
                taskInfo.CodeType   = RoslynCodeTaskFactoryCodeType.Class;
                taskInfo.SourceCode = File.ReadAllText(sourceAttribute.Value.Trim());
            }
            else if (typeAttribute != null)
            {
                if (String.IsNullOrWhiteSpace(typeAttribute.Value))
                {
                    // A <Code Type="" /> is not allowed
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.AttributeEmptyWithElement", "Type", "Code");
                    return(false);
                }

                // Attempt to parse the code type as a CodeTaskFactoryCodeType
                if (!Enum.TryParse(typeAttribute.Value.Trim(), ignoreCase: true, result: out RoslynCodeTaskFactoryCodeType codeType))
                {
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidCodeType", typeAttribute.Value, String.Join(", ", Enum.GetNames(typeof(RoslynCodeTaskFactoryCodeType))));
                    return(false);
                }

                taskInfo.CodeType = codeType;
            }

            if (languageAttribute != null)
            {
                if (String.IsNullOrWhiteSpace(languageAttribute.Value))
                {
                    // A <Code Language="" /> is not allowed
                    log.LogErrorWithCodeFromResources("CodeTaskFactory.AttributeEmptyWithElement", "Language", "Code");
                    return(false);
                }

                if (ValidCodeLanguages.ContainsKey(languageAttribute.Value))
                {
                    // The user specified one of the primary code languages using our vernacular
                    taskInfo.CodeLanguage = languageAttribute.Value.ToUpperInvariant();
                }
                else
                {
                    bool foundValidCodeLanguage = false;

                    // Attempt to map the user specified value as an alias to our vernacular for code languages
                    foreach (string validLanguage in ValidCodeLanguages.Keys)
                    {
                        if (ValidCodeLanguages[validLanguage].Contains(languageAttribute.Value))
                        {
                            taskInfo.CodeLanguage  = validLanguage;
                            foundValidCodeLanguage = true;
                            break;
                        }
                    }

                    if (!foundValidCodeLanguage)
                    {
                        // The user specified a code language we don't support
                        log.LogErrorWithCodeFromResources("CodeTaskFactory.InvalidCodeLanguage", languageAttribute.Value, String.Join(", ", ValidCodeLanguages.Keys));
                        return(false);
                    }
                }
            }

            if (String.IsNullOrWhiteSpace(taskInfo.SourceCode))
            {
                // The user did not specify a path to source code or source code within the <Code /> element.
                log.LogErrorWithCodeFromResources("CodeTaskFactory.NoSourceCode");
                return(false);
            }

            taskInfo.SourceCode = GetSourceCode(taskInfo, parameters);

            return(true);
        }
Пример #4
0
        /// <summary>
        /// Gets the full source code by applying an appropriate template based on the current <see cref="RoslynCodeTaskFactoryCodeType"/>.
        /// </summary>
        internal static string GetSourceCode(RoslynCodeTaskFactoryTaskInfo taskInfo, ICollection <TaskPropertyInfo> parameters)
        {
            if (taskInfo.CodeType == RoslynCodeTaskFactoryCodeType.Class)
            {
                return(taskInfo.SourceCode);
            }

            CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration
            {
                IsClass        = true,
                Name           = taskInfo.Name,
                TypeAttributes = TypeAttributes.Public,
                Attributes     = MemberAttributes.Final
            };

            codeTypeDeclaration.BaseTypes.Add("net.r_eg.IeXod.Utilities.Task");

            foreach (TaskPropertyInfo propertyInfo in parameters)
            {
                CreateProperty(codeTypeDeclaration, propertyInfo.Name, propertyInfo.PropertyType);
            }

            if (taskInfo.CodeType == RoslynCodeTaskFactoryCodeType.Fragment)
            {
                CodeMemberProperty successProperty = CreateProperty(codeTypeDeclaration, "Success", typeof(bool), true);

                CodeMemberMethod executeMethod = new CodeMemberMethod
                {
                    Name = "Execute",
                    // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
                    Attributes = MemberAttributes.Override | MemberAttributes.Public,
                    ReturnType = new CodeTypeReference(typeof(Boolean))
                };
                executeMethod.Statements.Add(new CodeSnippetStatement(taskInfo.SourceCode));
                executeMethod.Statements.Add(new CodeMethodReturnStatement(new CodePropertyReferenceExpression(null, successProperty.Name)));
                codeTypeDeclaration.Members.Add(executeMethod);
            }
            else
            {
                codeTypeDeclaration.Members.Add(new CodeSnippetTypeMember(taskInfo.SourceCode));
            }

            CodeNamespace codeNamespace = new CodeNamespace("InlineCode");

            codeNamespace.Imports.AddRange(DefaultNamespaces.Union(taskInfo.Namespaces, StringComparer.OrdinalIgnoreCase).Select(i => new CodeNamespaceImport(i)).ToArray());

            codeNamespace.Types.Add(codeTypeDeclaration);

            CodeCompileUnit codeCompileUnit = new CodeCompileUnit();

            codeCompileUnit.Namespaces.Add(codeNamespace);

            using (CodeDomProvider provider = CodeDomProvider.CreateProvider(taskInfo.CodeLanguage))
            {
                using (StringWriter writer = new StringWriter(new StringBuilder(), CultureInfo.CurrentCulture))
                {
                    provider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions
                    {
                        BlankLinesBetweenMembers = true,
                        VerbatimOrder            = true
                    });

                    return(writer.ToString());
                }
            }
        }