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`拿到的金鑰貼上後 會得到(比較長)加密後的金鑰 . 這組(比較長)加密後的金鑰 就可以任意丟在網路上沒問題了 . # 後言 在摸了半天,知道整個運作流程後 就沒那麼難了 . 大家加油ㄅ .

2019年9月2日 星期一

[開箱]少女終末旅行 千都 黏土人

前言 :
之前被問到要不要先到博弈遊戲公司幫忙
雖然算是有點逼不得已(?)結果不才居然答應了
三洨
.
然後第一次領到超級厚的薪水袋時
阿斯
.
之後就看著辦吧ODO
那邊好像是多角經營,想點辦法盡量不要去做博弈遊戲部分吧
.
是說就算真的只是普通的遊戲公司
好像也沒好到哪就是了
不才討厭做遊戲
.
寫遊戲又不好賺
薪水低
沒加班還要感謝公司感謝得五體投地
.
來寫後端多輕鬆
是不是呢(#
.
然後這次的黏土人一次買了兩隻
所以就分兩次開箱文吧
.

正文 :


開箱
一次開兩隻
.
有錢人的快樂
往往就是這麽樸實無華
且枯燥
.
ㄍ你角川
.
雖然說有兩隻
但這次開的是左邊的千都
.

.
正面配件
.
背面的頭盔
.
沒戴頭盔的正面
.
側面
.
背面
.
另外一個側面
.
戴上帽子的正面
看起來可愛多了
.
側面
.
背面
.
另外一個側面
.
不知道為什麼
支架很難安裝上去
.
背包
.
經過了幾百隻黏土人的進化
和喪女的背包比起來
這個好裝多了
.
只要往後卡就可以了
而且材質是軟的
不用像喪女的還要擔心會不會把背包的肩帶弄斷
.
然後有付上一根塑膠棒
插在背包上就可以固定黏土人了
.
但很詭異的,黏土人會懸空
.
最後還是決定打開說明書看看
確認之前安裝的方式沒有問題
.
除了原本的臉,還有另外付上兩張表情
.
不是不才在吐槽
把表情遮住的話
前面兩張臉根本看不出差別阿
.
原本的臉
.
嘴巴表情加上幾個pixel後的笑臉
.
額...好吧
.
然後上看智障的臉
.
這時候千續就派上用場了
.
ㄌㄨㄝ
.
哦幹幹幹最好不要過來喔
.
 (千都)乾我以後絕對不要成為這種人
.
下一個就換你了
.
幹到的相機超讚的啦
.
 千都的日記
.
 腳配件
.
 ......
.
 應該是這樣插下去吧?
.
雖然說說明書沒寫但不才直覺好像不是這樣
這個腳部配件應該是配合(DLC)半履帶型摩托車用的
.
然後意外的發現可以不需要配件就可以直立(!!!!!)
.
只是不知道為什麼
總覺得
突然想到植物大戰殭屍裡面的殭屍
.
換上喪女臉後
更像僵屍了
.
走開
.

總之今天的黏土人開箱就寫到這邊
下一篇是尤莉的黏土人開箱