2019年9月20日 星期五
[教學]如何把Github上面的.netStandard專案自動部屬到Nuget.org(使用Appveyor和build.cake)
# 前言 :
最近在寫專案
想說趁機試一下如何把自動化佈署
把`.netStandard`的專案部屬到`nuget`上
.
結果找了好久發現大家教學寫得都不一樣
三洨
.
找了找最後覺得[這篇](https://dev.to/joaofbantunes/creating-a-cicd-pipeline-for-a-net-library-part-1---intro-2j61?fbclid=IwAR2P8wr3x75airwMQLkpyFC73DZFDXF1l1JZna_XfvG_7L2mdXa45kQo674)寫得最好
就整理一下心得好了
.
# 佈署方式 :
簡單來說,CI(持續整合) CD(自動佈署)有好幾種方法
最基本的兩種
1. CI/CD 供應商(例如`Appveyor`)會提供自己的設定檔和格式,通常是`appveyor.yml `
可以在裡面設定一些參數
優點是快速上手,通常自動測試設定很快就完成了
但缺點是不同的自動化平台的設定方式會有些差別
2. 叫CI跑自己設定好的腳本
雖然說多了幾個不明所以的檔案會有點礙眼
但如果哪天要從`appveyor`換到`Tr@vis CI`,只要讓`travis.yml`跑自己寫好的腳本就好了
.
還有本地也可以執行腳本,不需要等到push後才知道跑出來的結果
.
內建的參數有點玩膩了(並沒有
就來玩玩看用腳本自動佈署的方式好了
.
# 執行流程
先簡單說明一下執行過程好了,以`Appveyor`為例
在跑CI/CD時
.
執行的流程大概會是 :
1. `Appveyor`會抓取專案內的`appveyor.yml`設定
2. 然後讓`Appveyor`執行`build.ps1`
並且帶入參數,例如`目前執行的branch`,還有`nuget上傳的金鑰`等等
3. `build.ps1`再執行`build.cake`,會把剛剛的那些參數傳過去
4. `build.cake`會定義一些流程,然後可以根據`不同參數`執行`不同動作`
假如在`master`上,就會自動佈署
如果是`develop`或是其他Branch,就只跑測試就好
.
所以說,跑腳本可以做的事情,會比`appveyor.yml`上面設定參數的自由度更高
.
# 腳本(build.cake)
這邊的說明方式是直接把整份`build.cake`直接翻開來加上註解做說明
也可以從[這邊](https://github.com/osu-Karaoke/LyricMaker/blob/develop/build.cake)看
.
首先,先產生`build.ps1`檔
詳細的方法可以參考[這邊](https://dotblogs.com.tw/mileslin/2016/04/23/124859)
.
自動產生出來的`build.ps1`不用動他
.
接下來新增`build.cake`
然後複製[這邊](https://github.com/osu-Karaoke/LyricMaker/blob/develop/build.cake)的內容過去
然後在修改成自己想要的樣子
.
這邊簡單說明一下這份`build.cake`工作方式是
平常PR時跑測試
如果在`master`上做事或是有東西合併進來,就佈署一份到`nuget.org`上
.
如果大家要做的事情也差不多
就只要修改`上面的參數名稱`就好了
```csharp
// 這邊不用管他,簡單來說當作要引用nuget的感覺
#tool "nuget:?package=coveralls.io&version=1.4.2"
#addin Cake.Git
#addin nuget:?package=Nuget.Core
#addin "nuget:?package=Cake.Coveralls&version=0.9.0"
// 因為要丟到nuget上所以using這個package
using NuGet;
// 預設要執行的項目
var target = Argument("target", "Default");
// 產生出來的nuget要放這個目錄
var artifactsDir = "./artifacts/";
// 方案位置,要改成自己的方案名稱
var solutionPath = "./LyricMaker.sln";
// 要包成nuget的專案,要改成自己的方案名稱
var project = "./LyricMaker/LyricMaker.csproj";
// 測試目錄,和對應需要測試的方案名稱,也是要改
var testFolder = "./LyricMaker.Tests/";
var testProject = testFolder + "LyricMaker.Tests.csproj";
// CodeCoverage清單,但不才暫時沒需要所以有註解調
var coverageResultsFileName = "coverage.xml";
// 從丟進來的參數中取得目前Branch名稱
var currentBranch = Argument("currentBranch", GitBranchCurrent("./").FriendlyName);
// 判斷是不是master
var isReleaseBuild = string.Equals(currentBranch, "master", StringComparison.OrdinalIgnoreCase);
// 預設是Release
var configuration = "Release";
// 從丟進來的參數中取得nuget金鑰和codecoverage(目前沒用到)的金鑰
var nugetApiKey = Argument("nugetApiKey", null);
var coverallsToken = Argument("coverallsToken", null);
// nuget.org網址
var nugetSource = "https://api.nuget.org/v3/index.json";
// 定義任務,把空間清乾淨
Task("Clean")
.Does(() => {
if (DirectoryExists(artifactsDir))
{
DeleteDirectory(
artifactsDir,
new DeleteDirectorySettings {
Recursive = true,
Force = true
}
);
}
CreateDirectory(artifactsDir);
DotNetCoreClean(solutionPath);
});
// 定義任務,載入nuget
Task("Restore")
.Does(() => {
DotNetCoreRestore(solutionPath);
});
// 定義任務,建置
Task("Build")
.IsDependentOn("Clean")
.IsDependentOn("Restore")
.Does(() => {
DotNetCoreBuild(
solutionPath,
new DotNetCoreBuildSettings
{
Configuration = configuration
}
);
});
// 定義任務,跑測試
Task("Test")
.Does(() => {
var settings = new DotNetCoreTestSettings
{
ArgumentCustomization = args => args.Append("/p:CollectCoverage=true")
.Append("/p:CoverletOutputFormat=opencover")
//.Append("/p:CoverletOutput=./" + coverageResultsFileName)
};
DotNetCoreTest(testProject, settings);
//MoveFile(testFolder + coverageResultsFileName, artifactsDir + coverageResultsFileName);
});
// 定義任務,上傳跑任務時產生的Code coverage結果
Task("UploadCoverage")
.IsDependentOn("Test")
.Does(() =>
{
CoverallsIo(artifactsDir + coverageResultsFileName, new CoverallsIoSettings()
{
RepoToken = coverallsToken
});
});
// 定義任務,包成nuget
Task("Package")
.Does(() => {
var settings = new DotNetCorePackSettings
{
OutputDirectory = artifactsDir,
NoBuild = true
};
DotNetCorePack(project, settings);
});
// 定義任務,把nuget上傳
Task("Publish")
.IsDependentOn("Package")
.Does(() => {
var pushSettings = new DotNetCoreNuGetPushSettings
{
Source = nugetSource,
ApiKey = nugetApiKey
};
var pkgs = GetFiles(artifactsDir + "*.nupkg");
foreach(var pkg in pkgs)
{
if(!IsNuGetPublished(pkg))
{
Information($"Publishing \"{pkg}\".");
DotNetCoreNuGetPush(pkg.FullPath, pushSettings);
}
else {
// 如果看到這個訊息,代表專案的nuget版本號在nuget.org上已經有了
Information($"Bypassing publishing \"{pkg}\" as it is already published.");
}
}
});
// Check nuget package is published
private bool IsNuGetPublished(FilePath packagePath) {
var package = new ZipPackage(packagePath.FullPath);
var latestPublishedVersions = NuGetList(
package.Id,
new NuGetListSettings
{
Prerelease = true
}
);
return latestPublishedVersions.Any(p => package.Version.Equals(new SemanticVersion(p.Version)));
}
// 定義任務,這個任務要完成編譯和測試
Task("BuildAndTest")
.IsDependentOn("Build")
.IsDependentOn("Test");
// 定義任務,這個任務要完成編譯,測試和上傳結果
Task("CompleteWithoutPublish")
.IsDependentOn("Build")
.IsDependentOn("Test");
//.IsDependentOn("UploadCoverage");
if(isReleaseBuild)
{
// 定義完成任務
// 玩成任務就是執行編譯,測試和佈署到nuget
Information("Release build");
Task("Complete")
.IsDependentOn("Build")
.IsDependentOn("Test")
//.IsDependentOn("UploadCoverage")
.IsDependentOn("Publish");
}
else
{
// 定義完成任務
// 完成任務就只需要跑編譯和測試
Information("Development build");
Task("Complete")
.IsDependentOn("Build")
.IsDependentOn("Test");
//.IsDependentOn("UploadCoverage");
}
// 定義預設任務是 完成任務
Task("Default")
.IsDependentOn("Complete");
// 執行預設任務
RunTarget(target);
```
# 腳本(appveyor.yml)
接下來新增`build.cake`
然後複製[這邊](https://github.com/osu-Karaoke/LyricMaker/blob/develop/build.cake)的內容過去
也是在修改成自己想要的樣子
.
也是說明一下專案格式
這樣比較好懂
```yml
# 建置版本
version: "{build}"
# 使用的環境
image: Visual Studio 2017
# 定義只有在那些Branch上Appveyor才做事
# 因為不才希望丟Pull Request也要跑CI,所以就註解掉了
#branches:
# only:
# - master
# - develop
# 環境
environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
NUGET_API_KEY:
# 金藥需要加密,下面會說明
secure: SCmDP3tOoIfO4zexfzu07xjBnv1KDMNDljU1iKZqU1VCTwyZ8pcAi5aLa5SMmwzh
# Appveyor要做的事情就是執行build.ps1,build.ps1就會執行build.cake
# 然後帶入參數
build_script:
- ps: .\build.ps1 --currentBranch=$env:APPVEYOR_REPO_BRANCH --nugetApiKey=$env:NUGET_API_KEY --coverallsToken=$env:COVERALLS_TOKEN
# 把內建的測試和佈署關掉
test: off #tests handled by cake script
deploy: off #deploy handled by cake script
```
.
在把Nuget自動上傳到`nuget.org`時,會需要一組金鑰
取得金鑰的教學在[這邊](https://docs.microsoft.com/zh-tw/nuget/quickstart/create-and-publish-a-package-using-the-dotnet-cli#acquire-your-api-key)可以看到
.
直接把取得到的金鑰放在`appveyor.yml`
會讓所有取得金鑰的人
能夠直接用金鑰佈署專案,相當危險,所以需要加密
.
需要在[這邊](https://ci.appveyor.com/tools/encrypt)
把從`nuget.org`拿到的金鑰貼上後
會得到(比較長)加密後的金鑰
.
這組(比較長)加密後的金鑰
就可以任意丟在網路上沒問題了
.
# 後言
在摸了半天,知道整個運作流程後
就沒那麼難了
.
大家加油ㄅ
.
訂閱:
張貼留言 (Atom)
沒有留言:
張貼留言