經過上面的折騰,我成功用批處理編譯 .NET Framework 4.5.2 項目後, 我並未滿足, 我想要更方便的, 無須安裝那麼多操蛋的東西, 只需要有運行時環境就可以了, 行不行? 答案當然是可以的, 那便是近年漸火的 roslyn 開源項目.
原本我是使用批處理調用 MSBuild 完成解決方案編譯的,新版的 MSBuild 在 Visual Studio 2015 會自帶安裝.
當然在Visual Studio 2015 中,MSBuild 是一個獨立的安裝包,可以單獨安裝,而無須安裝 Visual Studio 2015.
剛開始,我在 Windows Server 2008 R2 上使用 MSBuild 編譯使用 .NET Framework 4.5.2 版本 開發的項目,也不是那麼順利的.
期間,遇到並且解決了很多問題,依次順序為:
1. Windows Server 2008 R2 沒有安裝 .NET Framework 4.5.2 ,這個安裝 .NET Framework 4.5.2 就解決了.
2. Windows Server 2008 R2 上沒有可以編譯 .NET Framework 4.5.2 版本項目的 MSBuild .
原因是 MSBuild 的版本問題,因為 .NET Framework 4.0 自帶的 MSBuild 不能識別 C# 6.0 語法特性.
對於這個問題,當時很糾結,因為那時我還不知道 MSBuild 有了獨立安裝包,以為想要用新版的 MSBuild 必須在伺服器上安裝 Visual Studio 2015.
後來我在visualstudio.com上找到了 MSBuild 獨立安裝包, 名為 Microsoft Build Tools 2015, 所以這個問題也算是解決了.
3. 在Windows Server 2008 R2 上用 MSBuild 2015 居然要安裝 .NET Framework 4.5.2 SDK ?
這個也是安裝 .NET Framework 4.5.2 Developer Pack 就可以解決了, 不過我沒安裝, 而是直接從本機上拷貝一份到伺服器上, 存放位置和本機的路徑一樣.
只要安裝了 Visual Studio 2015 ,那麼 SDK 的位置一般在(x86系統)C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2 ,
(x64系統)C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2.
4. 由於在項目中引用了第三方組件,而第三方組件又引用了一些 .NET Framework 4.0 的 dll, 而在引用第三方組件的項目中沒有引用第三方組件中引用了的 .NET Framework 4.0 的 dll.
一般情況下,用 Visual Studio 2015 進行編譯是沒有問題的. 當使用批處理進行編譯的時候, 問題就來了, 拋出了一對錯誤, 諸如 System.Object, Object 之類的錯誤, 比如:
error CS0012: The type 'Object' is defined in an assembly that is not referenced.
You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'.
The type 'System.Object' is defined in an assembly
that is not referenced. You must add a reference to assembly 'System.Runtime,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
發現同一依賴程式集的不同版本之間存在衝突
解決辦法就是在引用了第三方組件的項目中, 引用第三方組件中引用了的 .NET Framework 4.0 的 dll. 這樣批處理是可以成功執行完成編譯的了. 但 Visual Studio 2015 編譯卻報錯了.
於是折騰了一番, 敲定解決辦法是:
1.拷貝System.Runtime.dll到解決方案目錄(隨意, 我的是Library目錄)下.
2.直接打開需要引用的csproj文件,向其中添加:
<Reference Include="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\Library\System.Runtime.dll</HintPath>
<Private>True</Private>
</Reference>
3.向 Web.Config 的 runtime --> assemblyBinding 節添加:
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
到這裡, VS 編譯 和 批處理編譯都沒問題了.
MSBuild 批處理編譯 .NET Framework 4.5.2 項目的命令行:
ECHO 初始化變數
SET ProgramPath="C:\Program Files"
IF EXIST %windir%\SysWOW64 SET ProgramPath="C:\Program Files (x86)"
SET MsBuildExe="%ProgramPath%\MSBuild\14.0\Bin\MSBuild.exe" /t:rebuild /verbosity:q /p:Configuration=Release;FrameworkPathOverride="%ProgramPath%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /l:FileLogger,Microsoft.Build.Engine;verbosity=normal;encoding=utf-8;append=true;logfile=Build.log
ECHO 編譯解決方案
%MsBuildExe% "C:\work\CRM\CRM.VS2015.sln"
大功告成, 這樣就可以編譯 .NET Framework 4.5.2 項目了.
可是後來我為什麼要換成 roslyn 編譯呢? 這也是有原因的!
經過上面的折騰,我成功用批處理編譯 .NET Framework 4.5.2 項目後, 我並未滿足, 我想要更方便的, 無須安裝那麼多操蛋的東西, 只需要有運行時環境就可以了, 行不行?
答案當然是可以的, 那便是近年漸火的 roslyn 開源項目.
接下來, 我嘗試使用 roslyn 幫助我完成編譯 .NET Framework 4.5.2 項目.
首先, 用 Visual Studio 2015 Update 1 新建一個目標框架為 .NET Framework 4.5.2 的控制台 C# 項目, 為什麼一定要 Update 1, 不解釋, 照做就對了.
然後對這項目右鍵屬性, 更改程式集名稱為 RBuild .
其次, 在 Visual Studio 菜單欄中 工具 --> NuGet 包管理器 --> 程式包管理器控制台.
在控制台輸入指令: Install-Package Microsoft.CodeAnalysis 以及 Install-Package Microsoft.Net.Compilers.
安裝成功後,在項目中會有個包管理文件 packages.config
內容如下:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.CodeAnalysis" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.Common" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.VisualBasic" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.1.1" targetFramework="net452" />
<package id="Microsoft.Composition" version="1.0.27" targetFramework="net452" />
<package id="Microsoft.Net.Compilers" version="1.1.1" targetFramework="net452" developmentDependency="true" />
<package id="System.Collections" version="4.0.0" targetFramework="net452" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net452" />
<package id="System.Globalization" version="4.0.0" targetFramework="net452" />
<package id="System.IO" version="4.0.0" targetFramework="net452" />
<package id="System.Linq" version="4.0.0" targetFramework="net452" />
<package id="System.Reflection" version="4.0.0" targetFramework="net452" />
<package id="System.Reflection.Extensions" version="4.0.0" targetFramework="net452" />
<package id="System.Reflection.Metadata" version="1.1.0" targetFramework="net452" />
<package id="System.Reflection.Primitives" version="4.0.0" targetFramework="net452" />
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net452" />
<package id="System.Runtime" version="4.0.0" targetFramework="net452" />
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net452" />
<package id="System.Runtime.InteropServices" version="4.0.0" targetFramework="net452" />
<package id="System.Text.Encoding" version="4.0.0" targetFramework="net452" />
<package id="System.Text.Encoding.Extensions" version="4.0.0" targetFramework="net452" />
<package id="System.Threading" version="4.0.0" targetFramework="net452" />
</packages>
接著, 在 Program類 敲代碼:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
namespace WebAPI.Build.Roslyn
{
class Program
{
/// <summary>
/// 待寫入文件的log列表
/// </summary>
static List<string> Logs = new List<string>();
/// <summary>
/// 輸出文件,成功與否
/// </summary>
static Dictionary<string, bool> OutputFiles = new Dictionary<string, bool>();
static void Main(string[] args)
{
//命令行參數解析器
CommandLineArgumentParser arguments = CommandLineArgumentParser.Parse(args);
if (arguments.Has(ConfigInfo.Help) || arguments.Has(ConfigInfo.Question))
{
string helpFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "help.txt");
string[] contents = File.ReadAllLines(helpFile);
foreach (string content in contents)
{
Console.WriteLine(content);
}
return;
}
//解決方案路徑
string solutionUrl;
if (arguments.Has(ConfigInfo.SolutionUrl))
{
solutionUrl = arguments.Get(ConfigInfo.SolutionUrl).Next;
}
else
{
solutionUrl = GetAppSetting(ConfigInfo.SolutionUrl);
}
//輸出目錄
string outputDir;
if (arguments.Has(ConfigInfo.OutputDir))
{
outputDir = arguments.Get(ConfigInfo.OutputDir).Next;
}
else
{
outputDir = GetAppSetting(ConfigInfo.OutputDir);
}
//編譯屬性
string properties;
if (arguments.Has(ConfigInfo.Properties))
{
properties = arguments.Get(ConfigInfo.Properties).Next;
}
else
{
properties = GetAppSetting(ConfigInfo.Properties);
}
Dictionary<string, string> keyValues;
if (!string.IsNullOrEmpty(properties))
{
keyValues = new Dictionary<string, string>();
IEnumerable<string> props = properties.Split(';').Where(t => !string.IsNullOrWhiteSpace(t));
foreach (var item in props)
{
string[] prop = item.Split('=');
keyValues.Add(prop[0], prop[1]);
}
}
else
{
keyValues = null;
}
string logFile;
if (arguments.Has(ConfigInfo.LogFile))
{
logFile = arguments.Get(ConfigInfo.LogFile).Next;
}
else
{
logFile = GetAppSetting(ConfigInfo.LogFile);
}
if (!File.Exists(solutionUrl))
{
AddFormatPrint("The file specified does not exist.");
AddFormatPrint("FileName:" + solutionUrl);
}
else
{
AddFormatPrint("Start building solutions");
AddFormatPrint();
AddFormatPrint("Check output directory exists");
if (!Directory.Exists(outputDir))
{
AddFormatPrint("Create output directory:");
AddFormatPrint(outputDir);
Directory.CreateDirectory(outputDir);
AddFormatPrint("Output directory has been created successfully");
}
else
{
AddFormatPrint("Output directory already exists");
}
AddFormatPrint();
AddFormatPrint("Start compilation solution");
AddFormatPrint();
bool success = CompileSolution(solutionUrl, outputDir, keyValues);
AddFormatPrint();
if (success)
{
AddFormatPrint("Compilation completed successfully.");
}
else
{
AddFormatPrint("Compilation failed.");
}
}
foreach (string fullPathName in OutputFiles.Where(t => t.Value == false).Select(t => t.Key))
{
try
{
File.Delete(fullPathName);
}
catch
{
}
}
File.WriteAllLines(logFile, Logs);
#if DEBUG
AddFormatPrint("Press the any key to exit.");
Console.ReadKey();
#endif
}
/// <summary>
/// 編譯解決方案和輸出項目bin文件
/// </summary>
/// <param name="solutionUrl"></param>
/// <param name="outputDir"></param>
/// <param name="keyValues"></param>
/// <returns></returns>
private static bool CompileSolution(string solutionUrl, string outputDir, Dictionary<string, string> keyValues = null)
{
bool success = true;
MSBuildWorkspace workspace;
if (keyValues != null && keyValues.Any())
{
workspace = MSBuildWorkspace.Create(keyValues);
}
else
{
workspace = MSBuildWorkspace.Create();
}
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
{
Project project = solution.GetProject(projectId);
AddFormatPrint("Building: {0}", project.FilePath);
try
{
Compilation projectCompilation = project.GetCompilationAsync().Result;
if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
{
string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
string fullPathName = string.Format("{0}\\{1}", outputDir, fileName);
if (!OutputFiles.ContainsKey(fullPathName))
{
OutputFiles.Add(fullPathName, true);
}
var diagnostics = projectCompilation.GetDiagnostics();
var warnDiagnostics = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Warning).ToArray();
var errorDiagnostics = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).ToArray();
foreach (var e in errorDiagnostics.Concat(warnDiagnostics).ToArray())
{
AddFormatPrint("{0}: {1}", e.Severity.ToString(), e.ToString());
}
if (errorDiagnostics.Any())
{
OutputFiles[fullPathName] = false;
AddFormatPrint("Build failed.");
success = false;
}
else
{
AddFormatPrint("Build successfully.");
using (var stream = new MemoryStream())
{
EmitResult result = projectCompilation.Emit(stream);
AddFormatPrint("{0} --> {1}", project.Name, fullPathName);
if (result.Success)
{
using (FileStream file = File.Create(fullPathName))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
AddFormatPrint("Output successfully.");
}
else
{
OutputFiles[fullPathName] = false;
AddFormatPrint("Output failed.");
success = false;
}
}
}
AddFormatPrint();
}
else
{
AddFormatPrint("Build failed. {0}", project.FilePath);
success = false;
}
}
catch (AggregateException ex)
{
foreach (var ie in ex.InnerExceptions)
{
AddFormatPrint(ie.Message);
}
success = false;
}
catch (Exception ex)
{
AddFormatPrint(ex.Message);
success = false;
}
AddFormatPrint();
}
return success;
}
/// <summary>
/// 添加消息記錄和列印消息
/// </summary>
/// <param name="format"></param>
/// <param name="args"></param>
private static void AddFormatPrint(string format = "", params object[] args)
{
if (format == string.Empty)
{
Logs.Add(string.Empty);
Console.WriteLine();
}
else
{
string log = string.Format(format, args);
Logs.Add(log);
Console.WriteLine(log);
}
}
/// <summary>
/// 獲取配置值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private static string GetAppSetting(string key)
{
return ConfigurationManager.AppSettings[key] ?? ConfigurationManager.AppSettings[ConfigInfo.KeyValues[key]];
}
}
public struct ConfigInfo
{
/// <summary>
/// 解決方案路徑
/// </summary>
public const string SolutionUrl = "-s";
/// <summary>
/// 輸出目錄
/// </summary>
public const string OutputDir = "-o";
/// <summary>
/// 編譯屬性
/// </summary>
public const string Properties = "-p";
/// <summary>
/// 日誌文件名稱
/// </summary>
public const string LogFile = "-l";
/// <summary>
/// 幫助
/// </summary>
public const string Help = "-h";
/// <summary>
/// 提問
/// </summary>
public const string Question = "-?";
/// <summary>
/// 全稱鍵值對
/// </summary>
public static readonly Dictionary<string, string> KeyValues = new Dictionary<string, string> { { SolutionUrl, "solutionUrl" }, { OutputDir, "outputDir" }, { Properties, "properties" }, { LogFile, "logFile" } };
}
}
編譯完成後, 就可以在 CMD 命令行提示符用了.
這是 help.txt 內容以及用法:
Provide help information for commands.
RBuild [-s] [-o] [-p]
-s —— Solution Path, required.
Use: -s "C:\work\CRM\CRM.VS2015.sln"
-o —— Output Directory, required.
Use: -o "E:\work\CRM\Build\WebAPI\bin"
-p —— Build Properties, required.
Use: -p Configuration=Release;FrameworkPathOverride=C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2;key=value
-l —— Output Log Path, not required.
Use: -l "log.txt"
目前我只知道 roslyn 可以編譯項目, 但編譯後拷貝相關的非代碼文件到相應目錄這個功能卻沒有發現哪裡有, 只能通過批處理自己拷貝了.
我的用法:
ECHO 開始編譯解決方案
BuildTools\RBuild.exe -s "..\..\CRM\src\CRM.VS2015.sln" -o "CRM\bin" -p Configuration=Release -l Build.log
安裝包下載:
.NET Framework 4.5.2(運行時環境):https://www.microsoft.com/zh-cn/download/details.aspx?id=42642
.NET Framework 4.5.2 Language Pack(可選):https://www.microsoft.com/zh-cn/download/details.aspx?id=42641
.NET Framework 4.5.2 Developer Pack(SDK):https://www.microsoft.com/zh-CN/download/details.aspx?id=42637
Microsoft Build Tools 2015(MSBuild):https://www.microsoft.com/zh-cn/download/details.aspx?id=48159
引用資料:
http://www.cnblogs.com/walkerwang/p/3368986.html
http://yangpei.appsp0t.com/post/aglzfnlhbmdwZWlyDAsSBUVudHJ5GKEfDA
http://stackoverflow.com/questions/13280008/how-do-i-compile-a-c-sharp-solution-with-roslyn
http://www.cnblogs.com/linxuanchen/p/c-sharp-command-line-argument-parser.html
https://msdn.microsoft.com/zh-cn/library/ms164311.aspx
https://github.com/dotnet/roslyn