/// <summary> /// Compiles the project and produces an output assembly /// </summary> /// <returns> /// Full name of the output assembly file. /// </returns> public static CompileResult Compile(CompileProject project) { string outputDirectory = string.Empty; if (project == null) { throw new ArgumentNullException(CompileMessages.ProjectNull); } try { //Create output directory for the compile project necessary files outputDirectory = CreateBuildDirectory(); //Create necessary files for MSBuild CreateProjectFiles(project, outputDirectory); // Once all the temporary files are created, we can call MsBuild to compile. // NOTE: If you want to dynamic build, the Targect Framework of this project MUST be .NET Framework 4. // MUST NOT be the Client Profile, or you cannot reference Microsoft.Build.dll using (var projectPropertiesCollection = new ProjectCollection()) { var globalProperty = new Dictionary<string, string>(); var buildRequest = new BuildRequestData( Path.Combine(outputDirectory, project.ProjectName) + ".csproj", globalProperty, null, new[] { "Build" }, null); var buildResult = BuildManager.DefaultBuildManager.Build(new BuildParameters(projectPropertiesCollection), buildRequest); if (buildResult == null) { return new CompileResult(BuildResultCode.Failure, outputDirectory, null); } switch (buildResult.OverallResult) { case BuildResultCode.Success: var filePath = Path.Combine(outputDirectory, project.ProjectName) + ".dll"; var result = new CompileResult(buildResult.OverallResult, filePath, null); try { File.Delete(Path.Combine(outputDirectory, PrivateKeyName)); } catch (Exception) //Failure to delete key doesn't affect the result of the operation { } return result; default: throw new CompileException(CompileMessages.ErrorCompiling, buildResult.Exception); } } } catch (CompileException ex) { var result = new CompileResult(BuildResultCode.Failure, outputDirectory, ex); return result; } catch (AggregateException ex) { //More than one exception occured in one of the compile steps var result = new CompileResult(BuildResultCode.Failure, outputDirectory, ex); return result; } catch (ArgumentException ex) { var result = new CompileResult(BuildResultCode.Failure, outputDirectory, ex); return result; } catch (InvalidOperationException ex) { var result = new CompileResult(BuildResultCode.Failure, outputDirectory, ex); return result; } }
/// <summary> /// Attempt to warn the user if he is relying on unsigned assemblies in his workflow (an unsupported scenario). /// There are cases we won't catch though that the C# compiler will, involving generics like /// Variable of List of List of TypeFromUnsignedAssembly /// </summary> /// <param name="project">Project for the compile operation</param> private static void GetReferencedAssemblies(CompileProject project) { if (project == null) { throw new ArgumentNullException(CompileMessages.ProjectNull); } //Check for conflicts, e.g. references to the same type with two different versions. CheckForConflictingVersions(project.ReferencedTypes, project.ReferencedAssemblies, project.ProjectName); // Get the references that are not signed. For some purposes like security the tool only acepts signed assemblies. var unsigned = (from assemblyItem in project.ReferencedAssemblies where (assemblyItem.GetName().GetPublicKey().IfNotNull(key => key.Length) == 0) select assemblyItem.FullName).ToList(); if (unsigned.Any()) { string message = string.Format(CompileMessages.UnsignedAssemblies, project.ProjectName, string.Join(Environment.NewLine, unsigned)); throw new CompileException(message); } }
/// <summary> /// Creates all the necessary files for building an assembly based on a workflow. /// </summary> /// <param name="project">Project for the compile operation</param> /// <param name="path">Path of the output directory</param> private static void CreateProjectFiles(CompileProject project, string path) { if (null == project) { throw new ArgumentNullException(CompileMessages.ProjectNull); } if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(CompileMessages.OutputDirectoryNull); } try { //Generate project files Parallel.Invoke(() => GenerateVisualStudioProjectFile(project, path), () => GenerateAssemblyInfoFile(project.ProjectName, path, project.ProjectVersion), () => GeneratePrivateKeyFile(path), () => GenerateXamlFile(project.ProjectName, path, project.ProjectXaml)); } catch (PathTooLongException ex) { //The file path is too long for the OS. throw new CompileException(CompileMessages.ProjectFileNameTooLong, ex); } catch (IOException ex) { //An I/O error occurred while opening the file. throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } catch (UnauthorizedAccessException ex) { //path specified a file that is read-only or this operation is not supported on the current platform or //path specified a directory or the caller does not have the required permission. throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } catch (NotSupportedException ex) { //path is in an invalid format. throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } catch (SecurityException ex) { //The caller does not have the required permission. throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } catch (AggregateException ex) { //The caller does not have the required permission. throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } catch (OutOfMemoryException ex) { //Not enough memory for creating files or directories throw new CompileException(CompileMessages.ErrorCreatingFiles, ex); } }
/// <summary> /// Creates a Visual Studio project file (.csproj) based on a workflow, including its dependencies, /// to use later in MSBuild /// </summary> /// <param name="project">Project for the compile operation</param> /// <param name="path">Path of the output directory</param> private static void GenerateVisualStudioProjectFile(CompileProject project, string path) { if (project == null) { throw new ArgumentNullException(CompileMessages.ProjectNull); } if (path == null) { throw new ArgumentNullException(CompileMessages.OutputDirectoryNull); } //We will concatenate several strings for all the content of the project, so we use StringBuilder. var projectFileStringBuilder = new StringBuilder(); var referencesStringBuilder = new StringBuilder(); const string referenceFormat = "<Reference Include=\"{0}\"><HintPath>{1}</HintPath></Reference>{2}"; //Get referenced assemblies. We filter dynamic references because they will be resolved automatically by the compiler using reflection, //so there is no need to include them in the project file. GetReferencedAssemblies(project); foreach (Assembly asm in project.ReferencedAssemblies.Where(assembly => !assembly.IsDynamic)) { // Build a Reference Include line for each referenced assembly referencesStringBuilder.Append(string.Format(referenceFormat, asm.FullName, asm.Location, Environment.NewLine)); } projectFileStringBuilder.Append(Properties.Resources.WorkflowProjectTemplate); projectFileStringBuilder.Replace("[ASSEMBLYNAME]", project.ProjectName); projectFileStringBuilder.Replace("[REFERENCE]", referencesStringBuilder.ToString()); projectFileStringBuilder.Replace("[XAMLFILE]", project.ProjectName + ".xaml"); projectFileStringBuilder.Replace("[PRODUCTVERSION]", project.ProjectVersion); //Create Visual Studio Project File File.WriteAllText(path + @"\" + project.ProjectName + ".csproj", projectFileStringBuilder.ToString()); }
private static CompileProject GetCompileProject(WorkflowDesigner desinger) { MultipleAuthorService.UnassignAllTask(desinger); CompileProject compileProject = new CompileProject(); HashSet<Type> referencedTypes = GetReferencedTypes(desinger); HashSet<Assembly> referencedAssemblies = GetReferencedAssemblies(desinger, referencedTypes); compileProject.ReferencedAssemblies = referencedAssemblies; compileProject.ReferencedTypes = referencedTypes; compileProject.ProjectXaml = desinger.CompilableXaml(); return compileProject; }