記錄一下我在使用 Nuke 來建置專案時碰到一個奇妙狀況,以及跟 Nuke 開發者討論此問題的過程和一些心得。
內容綱要
根據錯誤訊息來研判,似乎是我的 build project 參考了多個版本的 GitVersion.Tool 套件。然而,我的 build project 完全沒有參考 GitVersion.Tool。
奇怪的是,如果我重新建立一個 Nuke 建置腳本專案,而且用一個簡單的 Console App 專案來測試建置腳本,卻不會發生上述錯誤。
經由比對兩個建置腳本專案,我發現只要在那個無法順利執行的 build 專案的 .csproj 檔案裡面加入一行
看來問題已經解決?也許,但不確定這是否為正解,畢竟跟錯誤訊息的意思兜不攏。
錯誤訊息明明說的是「多重參考了 GitVersion.Tool 套件」,可是解決問題的方法卻告訴我們相反的事:其實是遺漏參考了 GitVersion.Tool,導致建置過程找不到那個套件。
對此現象,我想 Nuke 作者 Matthias Koch 也許能提供一些線索或建議,於是到 Gitter 上面發問(後來移至 Slack 討論),對方也花了不少時間協助排除問題。
同樣的程式碼,在不同電腦上面執行,出現了不同的錯誤訊息。這表示很可能是環境或某個組態設定的差異所致。此時仍猜不出到底是哪裡的差異,於是 Koch 問我能否除錯看看。
除錯過程中發現一個有趣的地方:如果我用「加入專案參考」的方式把 Nuke.Common 專案加入我的 solution,打算以此方式來設定中斷點和單步除錯 Nuke 原始碼,在執行建置腳本時,卻不會出現 GitVersion.Tool 多重參考的錯誤訊息,反倒是出現跟 Koch 那邊一樣的錯誤:「找不到 GitVersion.Tool。」
我對此現象很好奇,但此時仍猜不出原因。(後來找到原因後,這現象便有了合理解釋)
既然「加入專案參考」的方式無法重現問題,Koch 建議我用 JetBrains 的 Rider 來除錯,因為 Rider 在 Step Into 某個函式時,若沒有原始碼,就會自動下載原始碼。
感謝 Koch 先生的建議,透過 Rider 強大的除錯功能,我似乎找到問題的原因:exception 是由 NuGetPackageResolver 的
然而 Koch 先生並不同意我的看法。後來,我描述了單步追蹤過程,搭配畫面截圖,甚至錄製了一個影片來呈現 exception 的「發源地」,可以肯定就在程式碼較深處的「那一行」
由於對方的環境無法重現問題,我只好再努力一下,提供更具體的線索:那個較為底層的 SingleOrDefault 呼叫所取得的結果有兩筆以上的資料(因而導致 exception),而那兩筆資料是存在本機的 NuGet 套件:System.Dynamic.Runtime。如下圖:
也就是說,Nuke 預期某個套件只能有一個,可是執行時卻找到了兩個。於是我去看本機的 NuGet 全域套件的所在目錄,果然:
當我把這兩個長得很像的目錄刪除其中一個,再次除錯時,便發現程式依然會出錯,只是錯誤的原因換成了另一個套件名稱,而且該套件一樣是位在上圖的目錄下。
於是,根據我目前對 Nuke 原始碼的理解,再加上前面的幾個現象,我得出結論:Nuke 建置時,會從當前的 build project 的 .csproj 取出參考的套件,然後又會針對這些套件逐一取得它們各自參考的套件,且反覆這個程序,直到找出所有的相依套件清單。就是在這個過程當中,由於我的 NuGet 全域套件資料夾底下有許多「可疑的重複套件」的資料夾,導致 Nuke 程式發生錯誤。
後來,我又錄製了一個影片。這次不錄程式碼的單步追蹤過程,而只是凸顯一個事實:如果我先前的研判正確,那麼只要我把整個 NuGet 全域套件資料夾清空,再去執行 Nuke 建置腳本,應該就不會再出現「GitVersion.Tool 重複參考」的錯誤。影片如下:
就如影片最後顯示的,把 NuGet 全域套件資料夾清空之後,再跑一次建置腳本,錯誤訊息便從「多重參考 GitVersion.Tool」變成了「找不到 GitVersion.Tool」了。這才是正確的錯誤訊息!
現在,前面的謎團和現象都可以兜得攏、說得通了。
解決方法也如開頭講的,在 build 專案的 .csproj 檔案中加入這行就行了:
內容綱要
- 問題描述
- 調查過程
- Lessons Learned
(寫得挺囉嗦,可直接看結論,畢竟我碰到的是比較特殊的狀況。)
問題描述
當我看到 Nuke 0.24.7 發布時,便將我的一個 .NET 專案裡面使用的 Nuke 套件更新到 0.24.7 版。更新之後,專案建置失敗,錯誤訊息是:Package 'GitVersion.Tool' is referenced with multiple versions. Use NuGetPackageResolver and SetToolPath.如下圖:
根據錯誤訊息來研判,似乎是我的 build project 參考了多個版本的 GitVersion.Tool 套件。然而,我的 build project 完全沒有參考 GitVersion.Tool。
奇怪的是,如果我重新建立一個 Nuke 建置腳本專案,而且用一個簡單的 Console App 專案來測試建置腳本,卻不會發生上述錯誤。
經由比對兩個建置腳本專案,我發現只要在那個無法順利執行的 build 專案的 .csproj 檔案裡面加入一行
<PackageDownload Include="GitVersion.Tool" Version="[5.1.1]" />
就解決了,像這樣:看來問題已經解決?也許,但不確定這是否為正解,畢竟跟錯誤訊息的意思兜不攏。
錯誤訊息明明說的是「多重參考了 GitVersion.Tool 套件」,可是解決問題的方法卻告訴我們相反的事:其實是遺漏參考了 GitVersion.Tool,導致建置過程找不到那個套件。
對此現象,我想 Nuke 作者 Matthias Koch 也許能提供一些線索或建議,於是到 Gitter 上面發問(後來移至 Slack 討論),對方也花了不少時間協助排除問題。
調查過程
我先提供了能夠重現問題的程式碼,放在 GitHub 上面讓對方測試(現在已經從 GitHub 上移除)。結果在他的環境上執行 Nuke build 時,出現的錯誤訊息卻是「找不到 GitVersion.Tool。」同樣的程式碼,在不同電腦上面執行,出現了不同的錯誤訊息。這表示很可能是環境或某個組態設定的差異所致。此時仍猜不出到底是哪裡的差異,於是 Koch 問我能否除錯看看。
除錯過程中發現一個有趣的地方:如果我用「加入專案參考」的方式把 Nuke.Common 專案加入我的 solution,打算以此方式來設定中斷點和單步除錯 Nuke 原始碼,在執行建置腳本時,卻不會出現 GitVersion.Tool 多重參考的錯誤訊息,反倒是出現跟 Koch 那邊一樣的錯誤:「找不到 GitVersion.Tool。」
我對此現象很好奇,但此時仍猜不出原因。(後來找到原因後,這現象便有了合理解釋)
既然「加入專案參考」的方式無法重現問題,Koch 建議我用 JetBrains 的 Rider 來除錯,因為 Rider 在 Step Into 某個函式時,若沒有原始碼,就會自動下載原始碼。
感謝 Koch 先生的建議,透過 Rider 強大的除錯功能,我似乎找到問題的原因:exception 是由 NuGetPackageResolver 的
GetGlobalInstalledPackage
方法所拋出來。具體來說,問題出在那個方法當中的某個 SingleOrDefault
呼叫,如下圖:然而 Koch 先生並不同意我的看法。後來,我描述了單步追蹤過程,搭配畫面截圖,甚至錄製了一個影片來呈現 exception 的「發源地」,可以肯定就在程式碼較深處的「那一行」
SingleOrDefault
呼叫,但是他依然認為這些都無法解釋「GitVersion.Tool 重複參考」的錯誤訊息。也許我真的弄錯方向、太快下結論了。由於對方的環境無法重現問題,我只好再努力一下,提供更具體的線索:那個較為底層的 SingleOrDefault 呼叫所取得的結果有兩筆以上的資料(因而導致 exception),而那兩筆資料是存在本機的 NuGet 套件:System.Dynamic.Runtime。如下圖:
也就是說,Nuke 預期某個套件只能有一個,可是執行時卻找到了兩個。於是我去看本機的 NuGet 全域套件的所在目錄,果然:
當我把這兩個長得很像的目錄刪除其中一個,再次除錯時,便發現程式依然會出錯,只是錯誤的原因換成了另一個套件名稱,而且該套件一樣是位在上圖的目錄下。
於是,根據我目前對 Nuke 原始碼的理解,再加上前面的幾個現象,我得出結論:Nuke 建置時,會從當前的 build project 的 .csproj 取出參考的套件,然後又會針對這些套件逐一取得它們各自參考的套件,且反覆這個程序,直到找出所有的相依套件清單。就是在這個過程當中,由於我的 NuGet 全域套件資料夾底下有許多「可疑的重複套件」的資料夾,導致 Nuke 程式發生錯誤。
後來,我又錄製了一個影片。這次不錄程式碼的單步追蹤過程,而只是凸顯一個事實:如果我先前的研判正確,那麼只要我把整個 NuGet 全域套件資料夾清空,再去執行 Nuke 建置腳本,應該就不會再出現「GitVersion.Tool 重複參考」的錯誤。影片如下:
就如影片最後顯示的,把 NuGet 全域套件資料夾清空之後,再跑一次建置腳本,錯誤訊息便從「多重參考 GitVersion.Tool」變成了「找不到 GitVersion.Tool」了。這才是正確的錯誤訊息!
現在,前面的謎團和現象都可以兜得攏、說得通了。
解決方法也如開頭講的,在 build 專案的 .csproj 檔案中加入這行就行了:
<PackageDownload Include="GitVersion.Tool" Version="[5.1.1]" />
Lessons Learned
- 溝通不容易,跟別人討論他寫的程式碼更不容易,而跟別人用英文來討論他寫的程式碼而且對方環境無法重現問題...... 😵
- 小心層層傳遞的
IEnumerable<T>
和委派,它們讓程式碼看起來簡潔優雅(甚至高端),但是有可能增加日後除錯和維護的麻煩。 - Rider 在除錯方面的功能很好用(其他功能我還沒特別去了解,故不敢說,不過這個 IDE 的口碑是很好的)。
- 正常來說,NuGet 的全域套件資料夾底下,所有的套件子目錄應該都是全部小寫來命名。像本文提到的現象,那些「System.Dynamic.Runtime.4.0.11」之類的資料夾,我也無從追查究竟是何時、由哪個應用程式產生的了。