├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── LocalAppVeyor.sln ├── README.md ├── appveyor.yml ├── src ├── LocalAppVeyor.Engine │ ├── Configuration │ │ ├── AllowedJobFailureConditions.cs │ │ ├── AssemblyInfo.cs │ │ ├── Build.cs │ │ ├── BuildConfiguration.cs │ │ ├── BuildVerbosity.cs │ │ ├── EnvironmentVariables.cs │ │ ├── ExpandableString.cs │ │ ├── Matrix.cs │ │ ├── Reader │ │ │ ├── BuildConfigurationYamlFileReader.cs │ │ │ ├── BuildConfigurationYamlStringReader.cs │ │ │ ├── IBuildConfigurationReader.cs │ │ │ └── Internal │ │ │ │ ├── Converters │ │ │ │ ├── AllowedFailuresYamlTypeConverter.cs │ │ │ │ ├── EnvironmentVariablesYamlTypeConverter.cs │ │ │ │ └── VariableTypeConverter.cs │ │ │ │ └── Model │ │ │ │ ├── AllowedFailuresCollection.cs │ │ │ │ ├── InternalAssemblyVersion.cs │ │ │ │ ├── InternalBuild.cs │ │ │ │ ├── InternalBuildConfiguration.cs │ │ │ │ ├── InternalBuildVerbosity.cs │ │ │ │ ├── InternalConfigurations.cs │ │ │ │ ├── InternalEnvironmentVariables.cs │ │ │ │ ├── InternalMatrix.cs │ │ │ │ ├── InternalOperatingSystems.cs │ │ │ │ ├── InternalPlatforms.cs │ │ │ │ ├── InternalScriptBlock.cs │ │ │ │ ├── InternalScriptLine.cs │ │ │ │ └── InternalVariable.cs │ │ ├── ScriptBlock.cs │ │ ├── ScriptLine.cs │ │ ├── ScriptType.cs │ │ └── Variable.cs │ ├── Engine.cs │ ├── EngineConfiguration.cs │ ├── IPipelineOutputter.cs │ ├── Internal │ │ ├── BashScriptExecuter.cs │ │ ├── BatchScriptExecuter.cs │ │ ├── BuildPipelineExecuter.cs │ │ ├── ExecutionContext.cs │ │ ├── IEngineStep.cs │ │ ├── KnownExceptions │ │ │ └── SolutionNotFoundException.cs │ │ ├── PipelineOutputterMsBuildLogger.cs │ │ ├── Platform.cs │ │ ├── PowerShellScriptExecuter.cs │ │ └── Steps │ │ │ ├── AfterBuildStep.cs │ │ │ ├── AssemblyInfoRewriteStep.cs │ │ │ ├── BeforeBuildStep.cs │ │ │ ├── BuildScriptStep.cs │ │ │ ├── BuildStep.cs │ │ │ ├── CloneFolderStep.cs │ │ │ ├── InitStandardEnvironmentVariablesStep.cs │ │ │ ├── InitStep.cs │ │ │ ├── InstallStep.cs │ │ │ ├── OnFailureStep.cs │ │ │ ├── OnFinishStep.cs │ │ │ ├── OnSuccessStep.cs │ │ │ ├── ScriptBlockExecuterStep.cs │ │ │ └── TestScriptStep.cs │ ├── JobEndedEventArgs.cs │ ├── JobExecutionResult.cs │ ├── JobExecutionResultType.cs │ ├── JobStartingEventArgs.cs │ ├── LocalAppVeyor.Engine.csproj │ ├── LocalAppVeyorException.cs │ ├── MatrixJob.cs │ └── Properties │ │ └── AssemblyInfo.cs └── LocalAppVeyor │ ├── Commands │ ├── BuildConsoleCommand.cs │ ├── ConsoleCommand.cs │ ├── JobsConsoleCommand.cs │ └── LintConsoleCommand.cs │ ├── ConsoleOutputter.cs │ ├── LocalAppVeyor.csproj │ ├── Program.cs │ └── Properties │ └── launchSettings.json └── tests └── LocalAppVeyor.Engine.UnitTests ├── Configuration ├── AssemblyInfoTests.cs ├── BuildScriptTests.Unix.cs ├── BuildScriptTests.Window.cs ├── BuildScriptTests.cs ├── EnvironmentTests.cs ├── ExpandableStringTests.cs └── MatrixTests.cs ├── Engine ├── AssemblyInfoRewriteStepTests.cs ├── EngineTests.Unix.cs ├── EngineTests.Windows.cs └── EngineTests.cs ├── LocalAppVeyor.Engine.UnitTests.csproj ├── TestUtilities ├── UnixOnlyFactAttribute.cs └── WindowsOnlyFactAttribute.cs ├── packages.lock.json └── xunit.runner.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - dependencies 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: daily -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: ${{ matrix.os }} (${{ matrix.dotnet }}) 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | dotnet: ['8.0.100'] 14 | 15 | steps: 16 | - uses: actions/checkout@master 17 | 18 | - uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: ${{ matrix.dotnet }} 21 | 22 | - run: dotnet build --configuration Release 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | 56 | # StyleCop 57 | StyleCopReport.xml 58 | 59 | # Files built by Visual Studio 60 | *_i.c 61 | *_p.c 62 | *_i.h 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.iobj 67 | *.pch 68 | *.pdb 69 | *.ipdb 70 | *.pgc 71 | *.pgd 72 | *.rsp 73 | *.sbr 74 | *.tlb 75 | *.tli 76 | *.tlh 77 | *.tmp 78 | *.tmp_proj 79 | *.log 80 | *.vspscc 81 | *.vssscc 82 | .builds 83 | *.pidb 84 | *.svclog 85 | *.scc 86 | 87 | # Chutzpah Test files 88 | _Chutzpah* 89 | 90 | # Visual C++ cache files 91 | ipch/ 92 | *.aps 93 | *.ncb 94 | *.opendb 95 | *.opensdf 96 | *.sdf 97 | *.cachefile 98 | *.VC.db 99 | *.VC.VC.opendb 100 | 101 | # Visual Studio profiler 102 | *.psess 103 | *.vsp 104 | *.vspx 105 | *.sap 106 | 107 | # Visual Studio Trace Files 108 | *.e2e 109 | 110 | # TFS 2012 Local Workspace 111 | $tf/ 112 | 113 | # Guidance Automation Toolkit 114 | *.gpState 115 | 116 | # ReSharper is a .NET coding add-in 117 | _ReSharper*/ 118 | *.[Rr]e[Ss]harper 119 | *.DotSettings.user 120 | 121 | # JustCode is a .NET coding add-in 122 | .JustCode 123 | 124 | # TeamCity is a build add-in 125 | _TeamCity* 126 | 127 | # DotCover is a Code Coverage Tool 128 | *.dotCover 129 | 130 | # AxoCover is a Code Coverage Tool 131 | .axoCover/* 132 | !.axoCover/settings.json 133 | 134 | # Visual Studio code coverage results 135 | *.coverage 136 | *.coveragexml 137 | 138 | # NCrunch 139 | _NCrunch_* 140 | .*crunch*.local.xml 141 | nCrunchTemp_* 142 | 143 | # MightyMoose 144 | *.mm.* 145 | AutoTest.Net/ 146 | 147 | # Web workbench (sass) 148 | .sass-cache/ 149 | 150 | # Installshield output folder 151 | [Ee]xpress/ 152 | 153 | # DocProject is a documentation generator add-in 154 | DocProject/buildhelp/ 155 | DocProject/Help/*.HxT 156 | DocProject/Help/*.HxC 157 | DocProject/Help/*.hhc 158 | DocProject/Help/*.hhk 159 | DocProject/Help/*.hhp 160 | DocProject/Help/Html2 161 | DocProject/Help/html 162 | 163 | # Click-Once directory 164 | publish/ 165 | 166 | # Publish Web Output 167 | *.[Pp]ublish.xml 168 | *.azurePubxml 169 | # Note: Comment the next line if you want to checkin your web deploy settings, 170 | # but database connection strings (with potential passwords) will be unencrypted 171 | *.pubxml 172 | *.publishproj 173 | 174 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 175 | # checkin your Azure Web App publish settings, but sensitive information contained 176 | # in these scripts will be unencrypted 177 | PublishScripts/ 178 | 179 | # NuGet Packages 180 | *.nupkg 181 | # The packages folder can be ignored because of Package Restore 182 | **/[Pp]ackages/* 183 | # except build/, which is used as an MSBuild target. 184 | !**/[Pp]ackages/build/ 185 | # Uncomment if necessary however generally it will be regenerated when needed 186 | #!**/[Pp]ackages/repositories.config 187 | # NuGet v3's project.json files produces more ignorable files 188 | *.nuget.props 189 | *.nuget.targets 190 | 191 | # Microsoft Azure Build Output 192 | csx/ 193 | *.build.csdef 194 | 195 | # Microsoft Azure Emulator 196 | ecf/ 197 | rcf/ 198 | 199 | # Windows Store app package directories and files 200 | AppPackages/ 201 | BundleArtifacts/ 202 | Package.StoreAssociation.xml 203 | _pkginfo.txt 204 | *.appx 205 | 206 | # Visual Studio cache files 207 | # files ending in .cache can be ignored 208 | *.[Cc]ache 209 | # but keep track of directories ending in .cache 210 | !*.[Cc]ache/ 211 | 212 | # Others 213 | ClientBin/ 214 | ~$* 215 | *~ 216 | *.dbmdl 217 | *.dbproj.schemaview 218 | *.jfm 219 | *.pfx 220 | *.publishsettings 221 | orleans.codegen.cs 222 | 223 | # Including strong name files can present a security risk 224 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 225 | #*.snk 226 | 227 | # Since there are multiple workflows, uncomment next line to ignore bower_components 228 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 229 | #bower_components/ 230 | 231 | # RIA/Silverlight projects 232 | Generated_Code/ 233 | 234 | # Backup & report files from converting an old project file 235 | # to a newer Visual Studio version. Backup files are not needed, 236 | # because we have git ;-) 237 | _UpgradeReport_Files/ 238 | Backup*/ 239 | UpgradeLog*.XML 240 | UpgradeLog*.htm 241 | ServiceFabricBackup/ 242 | *.rptproj.bak 243 | 244 | # SQL Server files 245 | *.mdf 246 | *.ldf 247 | *.ndf 248 | 249 | # Business Intelligence projects 250 | *.rdl.data 251 | *.bim.layout 252 | *.bim_*.settings 253 | *.rptproj.rsuser 254 | 255 | # Microsoft Fakes 256 | FakesAssemblies/ 257 | 258 | # GhostDoc plugin setting file 259 | *.GhostDoc.xml 260 | 261 | # Node.js Tools for Visual Studio 262 | .ntvs_analysis.dat 263 | node_modules/ 264 | 265 | # Visual Studio 6 build log 266 | *.plg 267 | 268 | # Visual Studio 6 workspace options file 269 | *.opt 270 | 271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 272 | *.vbw 273 | 274 | # Visual Studio LightSwitch build output 275 | **/*.HTMLClient/GeneratedArtifacts 276 | **/*.DesktopClient/GeneratedArtifacts 277 | **/*.DesktopClient/ModelManifest.xml 278 | **/*.Server/GeneratedArtifacts 279 | **/*.Server/ModelManifest.xml 280 | _Pvt_Extensions 281 | 282 | # Paket dependency manager 283 | .paket/paket.exe 284 | paket-files/ 285 | 286 | # FAKE - F# Make 287 | .fake/ 288 | 289 | # JetBrains Rider 290 | .idea/ 291 | *.sln.iml 292 | 293 | # CodeRush 294 | .cr/ 295 | 296 | # Python Tools for Visual Studio (PTVS) 297 | __pycache__/ 298 | *.pyc 299 | 300 | # Cake - Uncomment if you are using it 301 | # tools/** 302 | # !tools/packages.config 303 | 304 | # Tabs Studio 305 | *.tss 306 | 307 | # Telerik's JustMock configuration file 308 | *.jmconfig 309 | 310 | # BizTalk build output 311 | *.btp.cs 312 | *.btm.cs 313 | *.odx.cs 314 | *.xsd.cs 315 | 316 | # OpenCover UI analysis results 317 | OpenCover/ 318 | 319 | # Azure Stream Analytics local run output 320 | ASALocalRun/ 321 | 322 | # MSBuild Binary and Structured Log 323 | *.binlog 324 | 325 | # NVidia Nsight GPU debugger configuration file 326 | *.nvuser 327 | 328 | # MFractors (Xamarin productivity tool) working folder 329 | .mfractor/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Joao Correia (@joaope) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LocalAppVeyor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29306.81 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{90C2048F-14A3-4CB7-86DD-B96708B01507}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | appveyor.yml = appveyor.yml 10 | LICENSE = LICENSE 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalAppVeyor.Engine", "src\LocalAppVeyor.Engine\LocalAppVeyor.Engine.csproj", "{86203454-E52C-49B4-BEDB-0BE025EA1D87}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalAppVeyor", "src\LocalAppVeyor\LocalAppVeyor.csproj", "{9A050F7B-1A34-47FD-805D-C8A0FEE102D0}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalAppVeyor.Engine.UnitTests", "tests\LocalAppVeyor.Engine.UnitTests\LocalAppVeyor.Engine.UnitTests.csproj", "{9C8D6864-9557-45E5-BB69-4658C7B66775}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {86203454-E52C-49B4-BEDB-0BE025EA1D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {86203454-E52C-49B4-BEDB-0BE025EA1D87}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {86203454-E52C-49B4-BEDB-0BE025EA1D87}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {86203454-E52C-49B4-BEDB-0BE025EA1D87}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {9A050F7B-1A34-47FD-805D-C8A0FEE102D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {9A050F7B-1A34-47FD-805D-C8A0FEE102D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {9A050F7B-1A34-47FD-805D-C8A0FEE102D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {9A050F7B-1A34-47FD-805D-C8A0FEE102D0}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {9C8D6864-9557-45E5-BB69-4658C7B66775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {9C8D6864-9557-45E5-BB69-4658C7B66775}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {9C8D6864-9557-45E5-BB69-4658C7B66775}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {9C8D6864-9557-45E5-BB69-4658C7B66775}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {B9111348-CC78-4DF2-8AF6-65634522D441} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | .NET Core global tool which brings _**appveyor.yml**_ to the center of your build process by making possible to execute 2 | its build jobs, locally. 3 | 4 | | Windows | OS X / Linux | Nuget | 5 | | ------------- |:-------------:| ----- | 6 | |[![Build status](https://ci.appveyor.com/api/projects/status/hpi2lwuhrr2qbhfm?svg=true)](https://ci.appveyor.com/project/joaope/localappveyor)|[![Build Status](https://github.com/joaope/LocalAppVeyor/workflows/Build/badge.svg)](https://github.com/joaope/LocalAppVeyor/actions)|[![Nuget](https://img.shields.io/nuget/v/LocalAppVeyor.svg?maxAge=0)](https://www.nuget.org/packages/LocalAppVeyor/)| 7 | 8 | - [How it works](#how-it-works) 9 | - [Install](#install) 10 | - [Usage](#usage) 11 | - [• `build` command](#%e2%80%a2-build-command) 12 | - [• `jobs` command](#%e2%80%a2-jobs-command) 13 | - [• `lint` command](#%e2%80%a2-lint-command) 14 | - [Supported build steps](#supported-build-steps) 15 | 16 | ## How it works 17 | LocalAppVeyor tries to strictly follow same [build pipeline](https://www.appveyor.com/docs/build-configuration/#build-pipeline) 18 | as [AppVeyor CI](https://appveyor.com) itself. 19 | 20 | 1. Grabs _appveyor.yml_'s build configuration from current (or specified) local repository folder. 21 | 2. Reads [supported build steps](#supported-build-steps) from it. 22 | 3. Executes [build pipeline](https://www.appveyor.com/docs/build-configuration/#build-pipeline) for each job (or specified ones) 23 | on the [build matrix](https://www.appveyor.com/docs/build-configuration/#build-matrix). 24 | 25 | Build engine tries to be the less intrusive as possible printing only what it comes from the build output. 26 | 27 | ## Install 28 | 29 | Install LocalAppVeyor as a [.NET Core CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) global tool using the following command: 30 | ```console 31 | dotnet tool install -g localappveyor 32 | ``` 33 | You have it now available on your command line: 34 | 35 | ```console 36 | LocalAppVeyor --help 37 | ``` 38 | 39 | *Requires [.NET Core 3.1](https://www.microsoft.com/net/download) or higher.* 40 | 41 | ## Usage 42 | ``` 43 | Usage: LocalAppVeyor [options] [command] 44 | 45 | Options: 46 | -?|-h|--help Show help information 47 | -v|--version Show version information 48 | 49 | Commands: 50 | build Executes one or all build jobs on specified repository directory 51 | jobs List all build jobs available to execution. 52 | lint Validates appveyor.yml YAML configuration. It requires internet connection. 53 | 54 | Use "LocalAppVeyor [command] --help" for more information about a command. 55 | ``` 56 | 57 | ### • `build` command 58 | 59 | This is the main console command which allows one to execute all or a smaller set of jobs from the 60 | [build matrix](https://www.appveyor.com/docs/build-configuration/#build-matrix). `--job` command should be followed by a integer 61 | corresponding to job index as listed on `jobs` command 62 | 63 | ``` 64 | Usage: LocalAppVeyor build [options] 65 | 66 | Options: 67 | -?|-h|--help Show help information 68 | -d|--dir Local repository directory where appveyor.yml sits. If not specified current directory is used 69 | -j|--job Job to build. You can specify multiple jobs. Use 'jobs' command to list all jobs 70 | -s|--skip Step to skip from the build pipeline. You can specify multiple steps. 71 | ``` 72 | 73 | ### • `jobs` command 74 | 75 | Lists all available jobs on the specified appveyor YAML configuration file build matrix. 76 | 77 | ``` 78 | Usage: LocalAppVeyor jobs [options] 79 | 80 | Options: 81 | -?|-h|--help Show help information 82 | -d|--dir Local repository directory where appveyor.yml sits. If not specified current directory is used 83 | ``` 84 | 85 | ### • `lint` command 86 | 87 | Validates appveyor.yml YAML configuration. It requires an active internet connection as it uses AppVeyor web API for a real and up to date validation. 88 | 89 | ``` 90 | Usage: LocalAppVeyor lint [options] 91 | 92 | Options: 93 | -?|-h|--help Show help information 94 | -t|--token AppVeyor account API token. If not specified it tries to get it from LOCALAPPVEYOR_API_TOKEN environment variable. You can find it here: https://ci.appveyor.com/api-token 95 | -d|--dir Local repository directory where appveyor.yml sits. If not specified current directory is used 96 | ``` 97 | 98 | ## Supported build steps 99 | Due to LocalAppVeyor's nature only a subset of [AppVeyor build steps](https://www.appveyor.com/docs/build-configuration/#build-pipeline) 100 | are supported. Some of them might get some support later in time, after consideration, but others most likely won't ever be part 101 | of the build pipeline. 102 | 103 | :white_check_mark: Fully supported   :large_blue_circle: Partially supported   :red_circle: Not yet supported 104 | 105 | | Step \ Option | Support | Notes | 106 | | ------------- |:-------------:| ----- | 107 | | version | :white_check_mark: | `{build}` placeholder is replaced by `0` 108 | | environment | :white_check_mark: | As for the [standard AppVeyor variables](https://www.appveyor.com/docs/environment-variables/) these are the ones supported: `APPVEYOR`, `CI`, `APPVEYOR_BUILD_FOLDER`, `APPVEYOR_BUILD_NUMBER`, `APPVEYOR_BUILD_VERSION`, `PLATFORM` and `CONFIGURATION` | 109 | | configuration | :white_check_mark: | | 110 | | platform | :white_check_mark: | | 111 | | os | :white_check_mark: | Relatively undocumented option but it exists apparently. It's usually a single value so it serves nothing other than to build the matrix job name. | 112 | | init | :white_check_mark: | | 113 | | clone_folder | :white_check_mark: | Tries first to clone to specified `clone_folder`, if any; otherwise it creates a random directory in user's temp folder. From this step on all scripts will be executed as the clone folder being the working directory. | 114 | | matrix | :white_check_mark: | | 115 | | install | :white_check_mark: | | 116 | | assembly_info | :white_check_mark: | | 117 | | before_build | :white_check_mark: | | 118 | | build | :white_check_mark: | | 119 | | build_script | :white_check_mark: | | 120 | | after_build | :white_check_mark: | | 121 | | before_test | :red_circle: | | 122 | | test | :red_circle: | | 123 | | test_script | :large_blue_circle: | It will always execute if it exists, no matter if other tests options are specified. | 124 | | after_test | :red_circle: | | 125 | | on_success | :white_check_mark: | | 126 | | on_failure | :white_check_mark: | | 127 | | on_finish | :white_check_mark: | | 128 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | version: 0.6.0+appveyor.{build} 4 | 5 | init: 6 | - dotnet --info 7 | - git config --global core.autocrlf true 8 | 9 | configuration: release 10 | 11 | build_script: 12 | - dotnet build src\LocalAppVeyor.Engine --configuration %configuration% 13 | - dotnet build src\LocalAppVeyor --configuration %configuration% 14 | 15 | after_build: 16 | - dotnet pack src\LocalAppVeyor.Engine --configuration %configuration% --output out_engine 17 | - dotnet pack src\LocalAppVeyor --configuration %configuration% --output out_console 18 | 19 | test_script: 20 | - dotnet test tests\LocalAppVeyor.Engine.UnitTests\LocalAppVeyor.Engine.UnitTests.csproj --configuration %configuration% 21 | 22 | artifacts: 23 | - path: out_engine\*.nupkg 24 | name: engine_packages 25 | - path: out_console\*.nupkg 26 | name: console_packages 27 | 28 | deploy: 29 | - provider: NuGet 30 | api_key: 31 | secure: 44crHq8PCXTO+ybHAUJjVHOT0PvFsqlcWH/m+/htVFXHihsmYj22WMnz1NBsDXWR 32 | on: 33 | branch: master 34 | appveyor_repo_tag: true 35 | configuration: release 36 | skip_symbols: true 37 | artifact: engine_packages 38 | 39 | - provider: NuGet 40 | api_key: 41 | secure: c1ZZc9Hqh92gxYb1pskP22xA+ppb5a3KQ6rESYDJnTn5jZv9PBjfxaRlDtWs765F 42 | on: 43 | branch: master 44 | appveyor_repo_tag: true 45 | configuration: release 46 | skip_symbols: true 47 | artifact: console_packages -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/AllowedJobFailureConditions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace LocalAppVeyor.Engine.Configuration; 6 | 7 | public class AllowedJobFailureConditions 8 | { 9 | public string OperatingSystem { get; } 10 | 11 | public string Configuration { get; } 12 | 13 | public string Platform { get; } 14 | 15 | public string TestCategory { get; } 16 | 17 | public ReadOnlyCollection Variables { get; } 18 | 19 | public AllowedJobFailureConditions( 20 | string operatingSystem, 21 | string configuration, 22 | string platform, 23 | string testCategory, 24 | IEnumerable variables) 25 | { 26 | OperatingSystem = operatingSystem; 27 | Configuration = configuration; 28 | Platform = platform; 29 | TestCategory = testCategory; 30 | Variables = new ReadOnlyCollection(variables?.ToList() ?? new List()); 31 | } 32 | 33 | public bool AreConditionsMetForJob(MatrixJob job) 34 | { 35 | if (job == null) 36 | { 37 | return false; 38 | } 39 | 40 | if (!string.IsNullOrEmpty(OperatingSystem)) 41 | { 42 | if (OperatingSystem != job.OperatingSystem) 43 | { 44 | return false; 45 | } 46 | } 47 | 48 | if (!string.IsNullOrEmpty(Configuration)) 49 | { 50 | if (Configuration != job.Configuration) 51 | { 52 | return false; 53 | } 54 | } 55 | 56 | if (!string.IsNullOrEmpty(Platform)) 57 | { 58 | if (Platform != job.Platform) 59 | { 60 | return false; 61 | } 62 | } 63 | 64 | if (Variables.Count > 0) 65 | { 66 | if (!Variables.All(v => job.Variables.Contains(v))) 67 | { 68 | return false; 69 | } 70 | } 71 | 72 | return true; 73 | } 74 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration; 2 | 3 | public class AssemblyInfo 4 | { 5 | public bool Patch { get; } 6 | 7 | public ExpandableString File { get; } 8 | 9 | public ExpandableString AssemblyVersion { get; } 10 | 11 | public ExpandableString AssemblyFileVersion { get; } 12 | 13 | public ExpandableString AssemblyInformationalVersion { get; } 14 | 15 | public AssemblyInfo() 16 | : this( 17 | false, 18 | null, 19 | null, 20 | null, 21 | null) 22 | { 23 | } 24 | 25 | public AssemblyInfo( 26 | bool patch, 27 | ExpandableString file, 28 | ExpandableString assemblyVersion, 29 | ExpandableString assemblyFileVersion, 30 | ExpandableString assemblyInformationalVersion) 31 | { 32 | Patch = patch; 33 | File = file; 34 | AssemblyVersion = assemblyVersion; 35 | AssemblyFileVersion = assemblyFileVersion; 36 | AssemblyInformationalVersion = assemblyInformationalVersion; 37 | } 38 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Build.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration; 2 | 3 | public class Build 4 | { 5 | public bool IsAutomaticBuildOff { get; } 6 | 7 | public bool IsParallel { get; } 8 | 9 | public ExpandableString SolutionFile { get; } 10 | 11 | public BuildVerbosity Verbosity { get; } 12 | 13 | public Build() 14 | : this(true, false, null, BuildVerbosity.Normal) 15 | { 16 | } 17 | 18 | public Build( 19 | bool isAutomaticBuildOff, 20 | bool isParallel, 21 | ExpandableString solutionFile, 22 | BuildVerbosity verbosity) 23 | { 24 | IsAutomaticBuildOff = isAutomaticBuildOff; 25 | IsParallel = isParallel; 26 | SolutionFile = solutionFile; 27 | Verbosity = verbosity; 28 | } 29 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/BuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace LocalAppVeyor.Engine.Configuration; 6 | 7 | public class BuildConfiguration 8 | { 9 | public static BuildConfiguration Default => new BuildConfiguration(); 10 | 11 | public ExpandableString Version { get; } 12 | 13 | public ScriptBlock InitializationScript { get; } 14 | 15 | public ExpandableString CloneFolder { get; } 16 | 17 | public Matrix Matrix { get; } 18 | 19 | public ScriptBlock InstallScript { get; } 20 | 21 | public AssemblyInfo AssemblyInfo { get; } 22 | 23 | public ReadOnlyCollection OperatingSystems { get; } 24 | 25 | public EnvironmentVariables EnvironmentVariables { get; } 26 | 27 | public ReadOnlyCollection Platforms { get; } 28 | 29 | public ReadOnlyCollection Configurations { get; } 30 | 31 | public Build Build { get; } 32 | 33 | public ScriptBlock BeforeBuildScript { get; } 34 | 35 | public ScriptBlock BuildScript { get; } 36 | 37 | public ScriptBlock AfterBuildScript { get; } 38 | 39 | public ScriptBlock TestScript { get; } 40 | 41 | public ScriptBlock OnSuccessScript { get; } 42 | 43 | public ScriptBlock OnFailureScript { get; } 44 | 45 | public ScriptBlock OnFinishScript { get; } 46 | 47 | private string[] _skipSteps = new string[0]; 48 | 49 | public string[] SkipSteps 50 | { 51 | get => _skipSteps; 52 | set => _skipSteps = value ?? new string[0]; 53 | } 54 | 55 | public BuildConfiguration() 56 | : this( 57 | null, 58 | null, 59 | null, 60 | null, 61 | null, 62 | new string[0], 63 | null, 64 | null, 65 | new string[0], 66 | new string[0], 67 | null, 68 | null, 69 | null, 70 | null, 71 | null, 72 | null, 73 | null, 74 | null) 75 | { 76 | } 77 | 78 | public BuildConfiguration( 79 | ExpandableString version, 80 | ScriptBlock initializationScript, 81 | ExpandableString cloneFolder, 82 | ScriptBlock installScript, 83 | AssemblyInfo assemblyInfo, 84 | IEnumerable operatingSystems, 85 | EnvironmentVariables environmentVariables, 86 | Matrix matrix, 87 | IEnumerable platforms, 88 | IEnumerable configurations, 89 | Build build, 90 | ScriptBlock beforeBuildScript, 91 | ScriptBlock buildScript, 92 | ScriptBlock afterBuildScript, 93 | ScriptBlock testScript, 94 | ScriptBlock onSuccess, 95 | ScriptBlock onFailure, 96 | ScriptBlock onFinish) 97 | { 98 | Version = version; 99 | InitializationScript = initializationScript ?? new ScriptBlock(); 100 | CloneFolder = cloneFolder; 101 | InstallScript = installScript ?? new ScriptBlock(); 102 | AssemblyInfo = assemblyInfo ?? new AssemblyInfo(); 103 | OperatingSystems = new ReadOnlyCollection(operatingSystems?.ToList() ?? new List()); 104 | EnvironmentVariables = environmentVariables ?? new EnvironmentVariables(); 105 | Matrix = matrix ?? new Matrix(); 106 | Platforms = new ReadOnlyCollection(platforms?.ToList() ?? new List()); 107 | Configurations = new ReadOnlyCollection(configurations?.ToList() ?? new List()); 108 | Build = build ?? new Build(); 109 | BeforeBuildScript = beforeBuildScript ?? new ScriptBlock(); 110 | BuildScript = buildScript ?? new ScriptBlock(); 111 | AfterBuildScript = afterBuildScript ?? new ScriptBlock(); 112 | TestScript = testScript ?? new ScriptBlock(); 113 | OnSuccessScript = onSuccess ?? new ScriptBlock(); 114 | OnFailureScript = onFailure ?? new ScriptBlock(); 115 | OnFinishScript = onFinish ?? new ScriptBlock(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/BuildVerbosity.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration; 2 | 3 | public enum BuildVerbosity 4 | { 5 | Quiet, 6 | Minimal, 7 | Normal, 8 | Detailed 9 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/EnvironmentVariables.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace LocalAppVeyor.Engine.Configuration; 6 | 7 | public class EnvironmentVariables 8 | { 9 | public ReadOnlyCollection CommonVariables { get; } 10 | 11 | public ReadOnlyCollection> Matrix { get; } 12 | 13 | public EnvironmentVariables() 14 | : this(new Variable[0], new List>()) 15 | { 16 | } 17 | 18 | public EnvironmentVariables(IEnumerable commonVariables) 19 | : this(commonVariables, null) 20 | { 21 | } 22 | 23 | public EnvironmentVariables(IEnumerable> matrixVariables) 24 | : this(null, matrixVariables) 25 | { 26 | } 27 | 28 | public EnvironmentVariables( 29 | IEnumerable commonVariables, 30 | IEnumerable> matrixVariables) 31 | { 32 | CommonVariables = new ReadOnlyCollection(commonVariables?.ToList() ?? new List()); 33 | Matrix = new ReadOnlyCollection>(matrixVariables?.ToList() ?? new List>()); 34 | } 35 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/ExpandableString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace LocalAppVeyor.Engine.Configuration; 5 | 6 | public struct ExpandableString 7 | { 8 | private static readonly Regex VarPattern = new Regex(@"\$\([\w-]+\)", RegexOptions.Compiled); 9 | 10 | private readonly string _internalStr; 11 | 12 | public static ExpandableString Empty => new ExpandableString(string.Empty); 13 | 14 | public ExpandableString(string str) 15 | { 16 | _internalStr = str; 17 | } 18 | 19 | public static implicit operator ExpandableString(string str) 20 | { 21 | return new ExpandableString(str); 22 | } 23 | 24 | public static implicit operator string(ExpandableString expandable) 25 | { 26 | if (string.IsNullOrEmpty(expandable._internalStr)) 27 | { 28 | return expandable._internalStr; 29 | } 30 | 31 | return VarPattern 32 | .Replace(expandable._internalStr, m => Environment.GetEnvironmentVariable(m.Value.Substring(2, m.Value.Length - 3))) 33 | .Replace("{build}", "0") 34 | .Replace("{version}", Environment.GetEnvironmentVariable("APPVEYOR_BUILD_VERSION")); 35 | } 36 | 37 | public override bool Equals(object obj) 38 | { 39 | switch (obj) 40 | { 41 | case null: 42 | return false; 43 | case string s: 44 | return this == s; 45 | case ExpandableString expandableString: 46 | return expandableString._internalStr == _internalStr; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | public override int GetHashCode() 53 | { 54 | return _internalStr.GetHashCode(); 55 | } 56 | 57 | public override string ToString() 58 | { 59 | return this; 60 | } 61 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Matrix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace LocalAppVeyor.Engine.Configuration; 6 | 7 | public class Matrix 8 | { 9 | public bool IsFastFinish { get; } 10 | 11 | public ReadOnlyCollection AllowedFailures { get; } 12 | 13 | public Matrix() 14 | : this(false, new AllowedJobFailureConditions[0]) 15 | { 16 | } 17 | 18 | public Matrix( 19 | bool isFastFinish, 20 | IEnumerable allowedFailures) 21 | { 22 | IsFastFinish = isFastFinish; 23 | AllowedFailures = new ReadOnlyCollection(allowedFailures?.ToList() ?? new List()); 24 | } 25 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/BuildConfigurationYamlFileReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Abstractions; 3 | 4 | namespace LocalAppVeyor.Engine.Configuration.Reader; 5 | 6 | public class BuildConfigurationYamlFileReader : IBuildConfigurationReader 7 | { 8 | private const string AppVeyorBuildFileName = "appveyor.yml"; 9 | 10 | public string YamlFilePath { get; } 11 | 12 | private readonly FileSystem _fileSystem; 13 | 14 | public BuildConfigurationYamlFileReader(string yamlFilePathOrDirectory) 15 | : this(new FileSystem(), yamlFilePathOrDirectory) 16 | { 17 | } 18 | 19 | public BuildConfigurationYamlFileReader( 20 | FileSystem fileSystem, 21 | string yamlFilePathOrDirectory) 22 | { 23 | if (string.IsNullOrEmpty(yamlFilePathOrDirectory)) throw new ArgumentNullException(nameof(yamlFilePathOrDirectory)); 24 | 25 | _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); 26 | 27 | string yamlFile; 28 | 29 | if (fileSystem.File.Exists(yamlFilePathOrDirectory)) 30 | { 31 | YamlFilePath = yamlFilePathOrDirectory; 32 | } 33 | else if (fileSystem.Directory.Exists(yamlFilePathOrDirectory) && 34 | fileSystem.File.Exists(yamlFile = fileSystem.Path.Combine(yamlFilePathOrDirectory, AppVeyorBuildFileName))) 35 | { 36 | YamlFilePath = yamlFile; 37 | } 38 | else 39 | { 40 | throw new LocalAppVeyorException($"'{AppVeyorBuildFileName}' file not found."); 41 | } 42 | } 43 | 44 | public BuildConfiguration GetBuildConfiguration() 45 | { 46 | string yaml; 47 | 48 | try 49 | { 50 | yaml = _fileSystem.File.ReadAllText(YamlFilePath); 51 | } 52 | catch (Exception e) 53 | { 54 | throw new LocalAppVeyorException("Error while reading YAML file.", e); 55 | } 56 | 57 | return new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/BuildConfigurationYamlStringReader.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration.Reader.Internal.Converters; 2 | using LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 3 | using YamlDotNet.Core; 4 | using YamlDotNet.Serialization; 5 | 6 | namespace LocalAppVeyor.Engine.Configuration.Reader; 7 | 8 | public class BuildConfigurationYamlStringReader : IBuildConfigurationReader 9 | { 10 | public string Yaml { get; } 11 | 12 | public BuildConfigurationYamlStringReader(string yaml) 13 | { 14 | Yaml = yaml; 15 | } 16 | 17 | public BuildConfiguration GetBuildConfiguration() 18 | { 19 | if (string.IsNullOrEmpty(Yaml)) 20 | { 21 | return BuildConfiguration.Default; 22 | } 23 | 24 | var yamlDeserializer = new DeserializerBuilder() 25 | .IgnoreUnmatchedProperties() 26 | .WithTypeConverter(new EnvironmentVariablesYamlTypeConverter()) 27 | .WithTypeConverter(new VariableTypeConverter()) 28 | .WithTypeConverter(new AllowedFailuresYamlTypeConverter()) 29 | .Build(); 30 | 31 | try 32 | { 33 | var conf = yamlDeserializer.Deserialize(Yaml); 34 | 35 | return conf?.ToBuildConfiguration() ?? BuildConfiguration.Default; 36 | } 37 | catch (YamlException e) 38 | { 39 | throw new LocalAppVeyorException("Error while parsing YAML.", e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/IBuildConfigurationReader.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration.Reader; 2 | 3 | public interface IBuildConfigurationReader 4 | { 5 | BuildConfiguration GetBuildConfiguration(); 6 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Converters/AllowedFailuresYamlTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | using YamlDotNet.Core; 5 | using YamlDotNet.Core.Events; 6 | using YamlDotNet.Serialization; 7 | 8 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Converters; 9 | 10 | internal class AllowedFailuresYamlTypeConverter : IYamlTypeConverter 11 | { 12 | private readonly IDeserializer _deserializer; 13 | 14 | public AllowedFailuresYamlTypeConverter() 15 | { 16 | _deserializer = new DeserializerBuilder() 17 | .IgnoreUnmatchedProperties() 18 | .WithTypeConverter(new VariableTypeConverter()) 19 | .Build(); 20 | } 21 | 22 | public bool Accepts(Type type) 23 | { 24 | return type == typeof(AllowedFailuresCollection); 25 | } 26 | 27 | public object ReadYaml(IParser parser, Type type) 28 | { 29 | var allowedFailuresCollection = new AllowedFailuresCollection(); 30 | 31 | // discard SequenceStart 32 | parser.Consume(); 33 | 34 | do 35 | { 36 | string os = null; 37 | string configuration = null; 38 | string platform = null; 39 | string testCategory = null; 40 | var variables = new List(); 41 | 42 | parser.Consume(); 43 | 44 | do 45 | { 46 | var possibleVar = _deserializer.Deserialize(parser); 47 | 48 | switch (possibleVar.Name) 49 | { 50 | case "os": 51 | os = possibleVar.Value; 52 | break; 53 | case "configuration": 54 | configuration = possibleVar.Value; 55 | break; 56 | case "platform": 57 | platform = possibleVar.Value; 58 | break; 59 | case "test_category": 60 | testCategory = possibleVar.Value; 61 | break; 62 | default: 63 | variables.Add(possibleVar.ToVariable()); 64 | break; 65 | } 66 | } while (!parser.Accept(out _)); 67 | 68 | parser.Consume(); 69 | 70 | allowedFailuresCollection.Add( 71 | new AllowedJobFailureConditions(os, configuration, platform, testCategory, variables.AsReadOnly())); 72 | 73 | } while (!parser.Accept(out _)); 74 | 75 | parser.Consume(); 76 | 77 | return allowedFailuresCollection; 78 | } 79 | 80 | public void WriteYaml(IEmitter emitter, object value, Type type) 81 | { 82 | throw new NotImplementedException(); 83 | } 84 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Converters/EnvironmentVariablesYamlTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | using YamlDotNet.Core; 5 | using YamlDotNet.Core.Events; 6 | using YamlDotNet.Serialization; 7 | 8 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Converters; 9 | 10 | internal class EnvironmentVariablesYamlTypeConverter : IYamlTypeConverter 11 | { 12 | private readonly IDeserializer _deserializer; 13 | 14 | public EnvironmentVariablesYamlTypeConverter() 15 | { 16 | _deserializer = new DeserializerBuilder() 17 | .IgnoreUnmatchedProperties() 18 | .WithTypeConverter(new VariableTypeConverter()) 19 | .Build(); 20 | } 21 | 22 | public bool Accepts(Type type) 23 | { 24 | return type == typeof(InternalEnvironmentVariables); 25 | } 26 | 27 | public object ReadYaml(IParser parser, Type type) 28 | { 29 | var env = new InternalEnvironmentVariables(); 30 | 31 | parser.Consume(); 32 | 33 | do 34 | { 35 | parser.Accept(out var scalar); 36 | 37 | if (scalar != null) 38 | { 39 | if (scalar.Value == "global") 40 | { 41 | // discard "global" value itself 42 | parser.Consume(); 43 | 44 | // read global variables (common to all matrix items) 45 | parser.Consume(); 46 | 47 | do 48 | { 49 | env.InternalCommonVariables.Add(_deserializer.Deserialize(parser)); 50 | 51 | } while (!parser.Accept(out _)); 52 | 53 | parser.Consume(); 54 | 55 | } 56 | else if (scalar.Value == "matrix") 57 | { 58 | // discard "matrix" value itself 59 | parser.Consume(); 60 | 61 | // discard SequenceStart 62 | parser.Consume(); 63 | 64 | do 65 | { 66 | var matrixItemVariables = new List(); 67 | 68 | parser.Consume(); 69 | 70 | do 71 | { 72 | matrixItemVariables.Add(_deserializer.Deserialize(parser)); 73 | 74 | } while (!parser.Accept(out _)); 75 | 76 | parser.Consume(); 77 | 78 | env.InternalMatrix.Add(matrixItemVariables.AsReadOnly()); 79 | 80 | } while (!parser.Accept(out _)); 81 | 82 | parser.Consume(); 83 | } 84 | else 85 | { 86 | var variable = _deserializer.Deserialize(parser); 87 | env.InternalCommonVariables.Add(variable); 88 | } 89 | } 90 | 91 | 92 | } while (!parser.Accept(out _)); 93 | 94 | parser.Consume(); 95 | 96 | return env; 97 | } 98 | 99 | public void WriteYaml(IEmitter emitter, object value, Type type) 100 | { 101 | throw new NotImplementedException(); 102 | } 103 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Converters/VariableTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 3 | using YamlDotNet.Core; 4 | using YamlDotNet.Core.Events; 5 | using YamlDotNet.Serialization; 6 | 7 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Converters; 8 | 9 | internal class VariableTypeConverter : IYamlTypeConverter 10 | { 11 | public bool Accepts(Type type) 12 | { 13 | return type == typeof(InternalVariable); 14 | } 15 | 16 | public object ReadYaml(IParser parser, Type type) 17 | { 18 | var name = parser.Consume().Value; 19 | 20 | parser.TryConsume(out var mappingStart); 21 | 22 | if (mappingStart != null) 23 | { 24 | var secureNode = parser.Consume(); 25 | 26 | if (secureNode != null && secureNode.Value == "secure") 27 | { 28 | var secureValue = parser.Consume().Value; 29 | 30 | parser.Consume(); 31 | 32 | return new InternalVariable(name, secureValue, true); 33 | } 34 | 35 | throw new YamlException("error parsing environment variables"); 36 | } 37 | 38 | return new InternalVariable(name, parser.Consume().Value, false); 39 | } 40 | 41 | public void WriteYaml(IEmitter emitter, object value, Type type) 42 | { 43 | throw new NotImplementedException(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/AllowedFailuresCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class AllowedFailuresCollection : Collection 6 | { 7 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalAssemblyVersion.cs: -------------------------------------------------------------------------------- 1 | using YamlDotNet.Serialization; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalAssemblyVersion 6 | { 7 | [YamlMember(Alias = "patch")] 8 | public bool Patch { get; set; } 9 | 10 | [YamlMember(Alias = "file")] 11 | public string File { get; set; } 12 | 13 | [YamlMember(Alias = "assembly_version")] 14 | public string AssemblyVersion { get; set; } 15 | 16 | [YamlMember(Alias = "assembly_file_version")] 17 | public string AssemblyFileVersion { get; set; } 18 | 19 | [YamlMember(Alias = "assembly_informational_version")] 20 | public string AssemblyInformationalVersion { get; set; } 21 | 22 | public AssemblyInfo ToAssemblyInfo() 23 | { 24 | return new AssemblyInfo( 25 | Patch, 26 | File, 27 | AssemblyVersion, 28 | AssemblyFileVersion, 29 | AssemblyInformationalVersion); 30 | } 31 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalBuild.cs: -------------------------------------------------------------------------------- 1 | using YamlDotNet.Serialization; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalBuild 6 | { 7 | [YamlIgnore] 8 | public bool IsAutomaticBuildOff { get; set; } 9 | 10 | [YamlMember(Alias = "parallel")] 11 | public bool IsParallel { get; set; } 12 | 13 | [YamlMember(Alias = "project")] 14 | public string SolutionFile { get; set; } 15 | 16 | public InternalBuildVerbosity? Verbosity { get; set; } 17 | 18 | public static implicit operator InternalBuild(string offString) 19 | { 20 | if (!string.IsNullOrEmpty(offString) && offString == "off") 21 | { 22 | return new InternalBuild 23 | { 24 | IsAutomaticBuildOff = true 25 | }; 26 | } 27 | 28 | return null; 29 | } 30 | 31 | public Build ToBuild() 32 | { 33 | return new Build( 34 | IsAutomaticBuildOff, 35 | IsParallel, 36 | SolutionFile, 37 | Verbosity?.ToBuildVerbosity() ?? BuildVerbosity.Normal); 38 | } 39 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalBuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | using YamlDotNet.Serialization; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalBuildConfiguration 6 | { 7 | [YamlMember(Alias = "version")] 8 | public string Version { get; set; } 9 | 10 | [YamlMember(Alias = "init")] 11 | public InternalScriptBlock InitializationScript { get; set; } 12 | 13 | [YamlMember(Alias = "clone_folder")] 14 | public string CloneFolder { get; set; } 15 | 16 | [YamlMember(Alias = "os")] 17 | public InternalOperatingSystems OperatingSystems { get; set; } 18 | 19 | [YamlMember(Alias = "environment")] 20 | public InternalEnvironmentVariables EnvironmentVariables { get; set; } 21 | 22 | [YamlMember(Alias = "matrix")] 23 | public InternalMatrix Matrix { get; set; } 24 | 25 | [YamlMember(Alias = "install")] 26 | public InternalScriptBlock InstallScript { get; set; } 27 | 28 | [YamlMember(Alias = "assembly_info")] 29 | public InternalAssemblyVersion AssemblyVersion { get; set; } 30 | 31 | [YamlMember(Alias = "platform")] 32 | public InternalPlatforms Platforms { get; set; } 33 | 34 | [YamlMember(Alias = "configuration")] 35 | public InternalConfigurations Configurations { get; set; } 36 | 37 | [YamlMember(Alias = "build")] 38 | public InternalBuild Build { get; set; } 39 | 40 | [YamlMember(Alias = "before_build")] 41 | public InternalScriptBlock BeforeBuildScript { get; set; } 42 | 43 | [YamlMember(Alias = "after_build")] 44 | public InternalScriptBlock AfterBuildScript { get; set; } 45 | 46 | [YamlMember(Alias = "build_script")] 47 | public InternalScriptBlock BuildScript { get; set; } 48 | 49 | [YamlMember(Alias = "test_script")] 50 | public InternalScriptBlock TestScript { get; set; } 51 | 52 | [YamlMember(Alias = "on_success")] 53 | public InternalScriptBlock OnSuccessScript { get; set; } 54 | 55 | [YamlMember(Alias = "on_failure")] 56 | public InternalScriptBlock OnFailureScript { get; set; } 57 | 58 | [YamlMember(Alias = "on_finish")] 59 | public InternalScriptBlock OnFinishScript { get; set; } 60 | 61 | public BuildConfiguration ToBuildConfiguration() 62 | { 63 | return new BuildConfiguration( 64 | Version, 65 | InitializationScript?.ToScriptBlock(), 66 | CloneFolder, 67 | InstallScript?.ToScriptBlock(), 68 | AssemblyVersion?.ToAssemblyInfo(), 69 | OperatingSystems, 70 | EnvironmentVariables?.ToEnvironmentVariables(), 71 | Matrix?.ToMatrix(), 72 | Platforms, 73 | Configurations, 74 | Build?.ToBuild(), 75 | BeforeBuildScript?.ToScriptBlock(), 76 | BuildScript?.ToScriptBlock(), 77 | AfterBuildScript?.ToScriptBlock(), 78 | TestScript?.ToScriptBlock(), 79 | OnSuccessScript?.ToScriptBlock(), 80 | OnFailureScript?.ToScriptBlock(), 81 | OnFinishScript?.ToScriptBlock()); 82 | } 83 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalBuildVerbosity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal enum InternalBuildVerbosity 6 | { 7 | Quiet, 8 | Minimal, 9 | Normal, 10 | Detailed 11 | } 12 | 13 | internal static class InternalBuildVerbosityExtensions 14 | { 15 | public static BuildVerbosity ToBuildVerbosity(this InternalBuildVerbosity buildVerbosity) 16 | { 17 | switch (buildVerbosity) 18 | { 19 | case InternalBuildVerbosity.Quiet: 20 | return BuildVerbosity.Quiet; 21 | case InternalBuildVerbosity.Minimal: 22 | return BuildVerbosity.Minimal; 23 | case InternalBuildVerbosity.Normal: 24 | return BuildVerbosity.Normal; 25 | case InternalBuildVerbosity.Detailed: 26 | return BuildVerbosity.Detailed; 27 | default: 28 | throw new ArgumentOutOfRangeException(nameof(buildVerbosity), buildVerbosity, null); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalConfigurations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalConfigurations : List 6 | { 7 | public static implicit operator InternalConfigurations(string platform) 8 | { 9 | return new InternalConfigurations 10 | { 11 | platform 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalEnvironmentVariables.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Linq; 3 | 4 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 5 | 6 | internal class InternalEnvironmentVariables 7 | { 8 | public readonly Collection InternalCommonVariables = new Collection(); 9 | 10 | public readonly Collection> InternalMatrix = 11 | new Collection>(); 12 | 13 | public EnvironmentVariables ToEnvironmentVariables() 14 | { 15 | return new EnvironmentVariables( 16 | InternalCommonVariables.Select(v => v.ToVariable()), 17 | InternalMatrix.Select(m => new ReadOnlyCollection(m.Select(v => v.ToVariable()).ToList()))); 18 | } 19 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalMatrix.cs: -------------------------------------------------------------------------------- 1 | using YamlDotNet.Serialization; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalMatrix 6 | { 7 | [YamlMember(Alias = "fast_finish")] 8 | public bool IsFastFinish { get; set; } 9 | 10 | [YamlMember(Alias = "allow_failures")] 11 | public AllowedFailuresCollection AllowedFailures { get; set; } 12 | 13 | public Matrix ToMatrix() 14 | { 15 | return new Matrix(IsFastFinish, AllowedFailures); 16 | } 17 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalOperatingSystems.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalOperatingSystems : List 6 | { 7 | public static implicit operator InternalOperatingSystems(string os) 8 | { 9 | return new InternalOperatingSystems 10 | { 11 | os 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalPlatforms.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 4 | 5 | internal class InternalPlatforms : List 6 | { 7 | public static implicit operator InternalPlatforms(string platform) 8 | { 9 | return new InternalPlatforms 10 | { 11 | platform 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalScriptBlock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 5 | 6 | internal class InternalScriptBlock : List 7 | { 8 | public ScriptBlock ToScriptBlock() 9 | { 10 | return new ScriptBlock(this.Select(l => l.ToScriptLine())); 11 | } 12 | 13 | public static implicit operator InternalScriptBlock(string _) 14 | { 15 | return new InternalScriptBlock(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalScriptLine.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Internal; 2 | using YamlDotNet.Serialization; 3 | 4 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 5 | 6 | internal class InternalScriptLine 7 | { 8 | [YamlMember(Alias = "cmd")] 9 | public string Batch { get; set; } 10 | 11 | [YamlMember(Alias = "ps")] 12 | public string PowerShell { get; set; } 13 | 14 | [YamlMember(Alias = "sh")] 15 | public string Bash { get; set; } 16 | 17 | public static implicit operator InternalScriptLine(string scriptLine) 18 | { 19 | if (Platform.IsUnix) 20 | { 21 | return new InternalScriptLine 22 | { 23 | Bash = scriptLine 24 | }; 25 | } 26 | 27 | return new InternalScriptLine 28 | { 29 | Batch = scriptLine 30 | }; 31 | } 32 | 33 | public ScriptLine ToScriptLine() 34 | { 35 | var scriptType = string.IsNullOrEmpty(PowerShell) 36 | ? string.IsNullOrEmpty(Batch) 37 | ? ScriptType.Bash 38 | : ScriptType.Batch 39 | : ScriptType.PowerShell; 40 | 41 | return new ScriptLine( 42 | scriptType, 43 | scriptType == ScriptType.PowerShell 44 | ? PowerShell 45 | : scriptType == ScriptType.Batch 46 | ? Batch 47 | : Bash); 48 | } 49 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Reader/Internal/Model/InternalVariable.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration.Reader.Internal.Model; 2 | 3 | internal class InternalVariable 4 | { 5 | public string Name { get; } 6 | 7 | public bool IsSecuredValue { get; } 8 | 9 | public string Value { get; } 10 | 11 | public InternalVariable(string name, string value, bool isSecuredValue) 12 | { 13 | Name = name; 14 | Value = value; 15 | IsSecuredValue = isSecuredValue; 16 | } 17 | 18 | public Variable ToVariable() 19 | { 20 | return new Variable(Name, Value, IsSecuredValue); 21 | } 22 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/ScriptBlock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration; 4 | 5 | public class ScriptBlock : List 6 | { 7 | public ScriptBlock() 8 | : this(new ScriptLine[0]) 9 | { 10 | } 11 | 12 | public ScriptBlock(IEnumerable scriptLines) 13 | : base(scriptLines) 14 | { 15 | } 16 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/ScriptLine.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration; 2 | 3 | public class ScriptLine 4 | { 5 | public ScriptType ScriptType { get; } 6 | 7 | public string Script { get; } 8 | 9 | public ScriptLine(ScriptType scriptType, string script) 10 | { 11 | ScriptType = scriptType; 12 | Script = script; 13 | } 14 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/ScriptType.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Configuration; 2 | 3 | public enum ScriptType 4 | { 5 | Batch, 6 | PowerShell, 7 | Bash 8 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Configuration/Variable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine.Configuration; 4 | 5 | public class Variable : IEquatable 6 | { 7 | public string Name { get; } 8 | 9 | public bool IsSecuredValue { get; } 10 | 11 | public ExpandableString Value { get; } 12 | 13 | public Variable(string name, string value, bool isSecuredValue) 14 | { 15 | Name = name; 16 | Value = value; 17 | IsSecuredValue = isSecuredValue; 18 | } 19 | 20 | public bool Equals(Variable other) 21 | { 22 | if (other == null) 23 | { 24 | return false; 25 | } 26 | 27 | return Name == other.Name && 28 | Value == other.Value && 29 | IsSecuredValue == other.IsSecuredValue; 30 | } 31 | 32 | public override string ToString() 33 | { 34 | return $"{Name}={Value}"; 35 | } 36 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Engine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LocalAppVeyor.Engine.Configuration; 5 | using LocalAppVeyor.Engine.Configuration.Reader; 6 | using LocalAppVeyor.Engine.Internal; 7 | 8 | namespace LocalAppVeyor.Engine; 9 | 10 | public sealed class Engine 11 | { 12 | public event EventHandler JobStarting = delegate { }; 13 | 14 | public event EventHandler JobEnded = delegate { }; 15 | 16 | private readonly BuildConfiguration _buildConfiguration; 17 | 18 | private readonly EngineConfiguration _engineConfiguration; 19 | 20 | private MatrixJob[] _jobs; 21 | 22 | public MatrixJob[] Jobs 23 | { 24 | get 25 | { 26 | if (_jobs != null) 27 | { 28 | return _jobs; 29 | } 30 | 31 | var environmentsVariables = _buildConfiguration.EnvironmentVariables.Matrix.Count > 0 32 | ? _buildConfiguration.EnvironmentVariables.Matrix.ToArray() 33 | : new IReadOnlyCollection[] { null }; 34 | var configurations = _buildConfiguration.Configurations.Count > 0 35 | ? _buildConfiguration.Configurations.ToArray() 36 | : new string[] { null }; 37 | var platforms = _buildConfiguration.Platforms.Count > 0 38 | ? _buildConfiguration.Platforms.ToArray() 39 | : new string[] { null }; 40 | var oses = _buildConfiguration.OperatingSystems.Count > 0 41 | ? _buildConfiguration.OperatingSystems.ToArray() 42 | : new string[] { null }; 43 | 44 | _jobs = ( 45 | from environmentVariables in environmentsVariables 46 | from configuration in configurations 47 | from platform in platforms 48 | from os in oses 49 | select new MatrixJob(os, environmentVariables, configuration, platform)) 50 | .ToArray(); 51 | 52 | return _jobs; 53 | } 54 | } 55 | 56 | public Engine( 57 | EngineConfiguration engineConfiguration, 58 | IBuildConfigurationReader buildConfigurationReader) 59 | : this(engineConfiguration, buildConfigurationReader.GetBuildConfiguration()) 60 | { 61 | } 62 | 63 | public Engine( 64 | EngineConfiguration engineConfiguration, 65 | BuildConfiguration buildConfiguration) 66 | { 67 | _buildConfiguration = buildConfiguration ?? throw new ArgumentNullException(nameof(buildConfiguration)); 68 | _engineConfiguration = engineConfiguration ?? throw new ArgumentNullException(nameof(engineConfiguration)); 69 | } 70 | 71 | public JobExecutionResult ExecuteJob(int jobIndex) 72 | { 73 | if (jobIndex < 0 || jobIndex >= Jobs.Length) 74 | { 75 | var result = JobExecutionResult.CreateJobNotFound(); 76 | JobEnded?.Invoke(this, new JobEndedEventArgs(null, result)); 77 | return result; 78 | } 79 | 80 | return ExecuteJob(Jobs[jobIndex]); 81 | } 82 | 83 | public JobExecutionResult ExecuteJob(MatrixJob job) 84 | { 85 | JobStarting?.Invoke(this, new JobStartingEventArgs(job)); 86 | 87 | var executionContext = new ExecutionContext( 88 | job, 89 | _buildConfiguration, 90 | _engineConfiguration.Outputter, 91 | _engineConfiguration.RepositoryDirectoryPath, 92 | !string.IsNullOrEmpty(_buildConfiguration.CloneFolder) 93 | ? _buildConfiguration.CloneFolder 94 | : new ExpandableString(_engineConfiguration.FallbackCloneDirectoryPath), 95 | _engineConfiguration.FileSystem); 96 | 97 | var executionResult = new BuildPipelineExecuter(executionContext).Execute(); 98 | 99 | JobEnded?.Invoke(this, new JobEndedEventArgs(job, executionResult)); 100 | 101 | return executionResult; 102 | } 103 | 104 | public JobExecutionResult[] ExecuteAllJobs() 105 | { 106 | var results = new JobExecutionResult[Jobs.Length]; 107 | 108 | for (var i = 0; i < Jobs.Length; i++) 109 | { 110 | var job = Jobs[i]; 111 | 112 | results[i] = ExecuteJob(job); 113 | 114 | // if success, or job is on the allowed failures matrix, continue on to next one 115 | if (results[i].IsSuccessfulExecution || 116 | _buildConfiguration.Matrix.AllowedFailures.Any(a => a.AreConditionsMetForJob(job))) 117 | { 118 | continue; 119 | } 120 | 121 | // if fast_finish is on mark remaining jobs as NotExecuted and leave build 122 | if (_buildConfiguration.Matrix.IsFastFinish) 123 | { 124 | for (++i; i < Jobs.Length; i++) 125 | { 126 | results[i] = JobExecutionResult.CreateNotExecuted(); 127 | } 128 | 129 | break; 130 | } 131 | } 132 | 133 | return results; 134 | } 135 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/EngineConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Abstractions; 3 | 4 | namespace LocalAppVeyor.Engine; 5 | 6 | public class EngineConfiguration 7 | { 8 | public string RepositoryDirectoryPath { get; } 9 | 10 | public string FallbackCloneDirectoryPath { get; } 11 | 12 | public IPipelineOutputter Outputter { get; } 13 | 14 | public IFileSystem FileSystem { get; } 15 | 16 | public EngineConfiguration( 17 | string repositoryDirectoryPath, 18 | string fallbackCloneDirectoryPath, 19 | IPipelineOutputter outputter, 20 | IFileSystem fileSystem) 21 | { 22 | if (string.IsNullOrEmpty(repositoryDirectoryPath)) throw new ArgumentNullException(nameof(repositoryDirectoryPath)); 23 | if (string.IsNullOrEmpty(fallbackCloneDirectoryPath)) throw new ArgumentNullException(nameof(fallbackCloneDirectoryPath)); 24 | 25 | RepositoryDirectoryPath = repositoryDirectoryPath; 26 | FallbackCloneDirectoryPath = fallbackCloneDirectoryPath; 27 | Outputter = outputter ?? throw new ArgumentNullException(nameof(outputter)); 28 | FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); 29 | } 30 | 31 | public EngineConfiguration( 32 | string repositoryDirectoryPath, 33 | IPipelineOutputter outputter, 34 | IFileSystem fileSystem) 35 | : this(repositoryDirectoryPath, GetFallbackTemporaryCloningFolder(fileSystem), outputter, fileSystem) 36 | { 37 | if (string.IsNullOrEmpty(repositoryDirectoryPath)) throw new ArgumentNullException(nameof(repositoryDirectoryPath)); 38 | 39 | RepositoryDirectoryPath = repositoryDirectoryPath; 40 | Outputter = outputter ?? throw new ArgumentNullException(nameof(outputter)); 41 | FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); 42 | } 43 | 44 | public EngineConfiguration( 45 | string repositoryDirectoryPath, 46 | IPipelineOutputter outputter) 47 | : this(repositoryDirectoryPath, outputter, new FileSystem()) 48 | { 49 | } 50 | 51 | private static string GetFallbackTemporaryCloningFolder(IFileSystem fileSystem) 52 | { 53 | if (fileSystem == null) throw new ArgumentNullException(nameof(fileSystem)); 54 | 55 | string tempDirectory; 56 | 57 | do 58 | { 59 | tempDirectory = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName()); 60 | } while (fileSystem.Directory.Exists(tempDirectory)); 61 | 62 | return tempDirectory; 63 | } 64 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/IPipelineOutputter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine; 4 | 5 | public interface IPipelineOutputter 6 | { 7 | void SetColor(ConsoleColor color); 8 | 9 | void ResetColor(); 10 | 11 | void Write(string message); 12 | 13 | void WriteSuccess(string successMessage); 14 | 15 | void WriteWarning(string warningMessage); 16 | 17 | void WriteError(string errorMessage); 18 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/BashScriptExecuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace LocalAppVeyor.Engine.Internal; 5 | 6 | internal static class BashScriptExecuter 7 | { 8 | public static bool Execute( 9 | string script, 10 | Action onOutputDataReceived, 11 | Action onErrorDataReceived) 12 | { 13 | if (string.IsNullOrEmpty(script)) 14 | { 15 | return true; 16 | } 17 | 18 | using (var process = Process.Start(new ProcessStartInfo 19 | { 20 | FileName = "/bin/bash", 21 | Arguments = $"-c \"{script.Replace("\"", "\\\"")}\"", 22 | CreateNoWindow = true, 23 | UseShellExecute = false, 24 | RedirectStandardError = true, 25 | RedirectStandardOutput = true 26 | })) 27 | { 28 | process.OutputDataReceived += (s, e) => 29 | { 30 | onOutputDataReceived?.Invoke(e.Data); 31 | }; 32 | process.ErrorDataReceived += (s, e) => 33 | { 34 | onErrorDataReceived?.Invoke(e.Data); 35 | }; 36 | 37 | process.BeginOutputReadLine(); 38 | process.BeginErrorReadLine(); 39 | 40 | process.WaitForExit(); 41 | 42 | return process.ExitCode == 0; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/BatchScriptExecuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO.Abstractions; 4 | 5 | namespace LocalAppVeyor.Engine.Internal; 6 | 7 | internal static class BatchScriptExecuter 8 | { 9 | public static bool Execute( 10 | IFileSystem fileSystem, 11 | string workingDirectory, 12 | string script, 13 | Action onOutputDataReceived, 14 | Action onErrorDataReceived) 15 | { 16 | if (string.IsNullOrEmpty(script)) 17 | { 18 | return true; 19 | } 20 | 21 | var batchFile = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), $"{Guid.NewGuid()}.bat"); 22 | fileSystem.File.WriteAllText(batchFile, script); 23 | 24 | using (var process = Process.Start(new ProcessStartInfo("cmd.exe", $"/c {batchFile}") 25 | { 26 | CreateNoWindow = true, 27 | UseShellExecute = false, 28 | RedirectStandardError = true, 29 | RedirectStandardOutput = true, 30 | WorkingDirectory = workingDirectory 31 | })) 32 | { 33 | process.OutputDataReceived += (s, e) => 34 | { 35 | onOutputDataReceived?.Invoke(e.Data); 36 | }; 37 | process.ErrorDataReceived += (s, e) => 38 | { 39 | onErrorDataReceived?.Invoke(e.Data); 40 | }; 41 | 42 | process.BeginOutputReadLine(); 43 | process.BeginErrorReadLine(); 44 | 45 | process.WaitForExit(); 46 | 47 | return process.ExitCode == 0; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/BuildPipelineExecuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using LocalAppVeyor.Engine.Internal.KnownExceptions; 4 | using LocalAppVeyor.Engine.Internal.Steps; 5 | 6 | namespace LocalAppVeyor.Engine.Internal; 7 | 8 | internal sealed class BuildPipelineExecuter 9 | { 10 | private readonly ExecutionContext _executionContext; 11 | 12 | private readonly InitStandardEnvironmentVariablesStep _environmentStep; 13 | private readonly InitStep _initStep; 14 | private readonly CloneFolderStep _cloneStep; 15 | private readonly InstallStep _installStep; 16 | private readonly AssemblyInfoRewriteStep _assemblyInfoStep; 17 | private readonly BeforeBuildStep _beforeBuildStep; 18 | private readonly BuildScriptStep _buildScriptStep; 19 | private readonly BuildStep _buildStep; 20 | private readonly AfterBuildStep _afterBuildStep; 21 | private readonly TestScriptStep _testScriptStep; 22 | 23 | private readonly OnSuccessStep _onSuccessStep; 24 | private readonly OnFailureStep _onFailureStep; 25 | private readonly OnFinishStep _onFinishStep; 26 | 27 | public BuildPipelineExecuter(ExecutionContext executionContext) 28 | { 29 | _executionContext = executionContext; 30 | 31 | _environmentStep = new InitStandardEnvironmentVariablesStep(); 32 | _initStep = new InitStep(executionContext.RepositoryDirectory, executionContext.BuildConfiguration.InitializationScript); 33 | _cloneStep = new CloneFolderStep(executionContext.FileSystem); 34 | _installStep = new InstallStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.InstallScript); 35 | _assemblyInfoStep = new AssemblyInfoRewriteStep(); 36 | _beforeBuildStep = new BeforeBuildStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.BeforeBuildScript); 37 | _buildScriptStep = new BuildScriptStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.BuildScript); 38 | _buildStep = new BuildStep(); 39 | _afterBuildStep = new AfterBuildStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.AfterBuildScript); 40 | _testScriptStep = new TestScriptStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.TestScript); 41 | 42 | _onSuccessStep = new OnSuccessStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.OnSuccessScript); 43 | _onFailureStep = new OnFailureStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.OnFailureScript); 44 | _onFinishStep = new OnFinishStep(executionContext.CloneDirectory, executionContext.BuildConfiguration.OnFinishScript); 45 | } 46 | 47 | public JobExecutionResult Execute() 48 | { 49 | JobExecutionResult executionResult; 50 | 51 | try 52 | { 53 | var isSuccess = ExecuteBuildPipeline(_executionContext); 54 | 55 | // on_success / on_failure only happens here, after we know the build status 56 | // they do intervene on build final status though 57 | isSuccess = isSuccess 58 | ? Execute(_onSuccessStep, _executionContext) 59 | : Execute(_onFailureStep, _executionContext); 60 | 61 | return isSuccess 62 | ? JobExecutionResult.CreateSuccess() 63 | : JobExecutionResult.CreateFailure(); 64 | } 65 | catch (SolutionNotFoundException) 66 | { 67 | executionResult = JobExecutionResult.CreateSolutionNotFound(); 68 | } 69 | catch (Exception e) 70 | { 71 | executionResult = JobExecutionResult.CreateUnhandledException(e); 72 | } 73 | finally 74 | { 75 | // on_finish don't influence build final status so we just run it 76 | Execute(_onFinishStep, _executionContext); 77 | } 78 | 79 | return executionResult; 80 | } 81 | 82 | private bool ExecuteBuildPipeline(ExecutionContext executionContext) 83 | { 84 | if (!Execute(_environmentStep, executionContext)) 85 | { 86 | return false; 87 | } 88 | 89 | if (!Execute(_initStep, executionContext)) 90 | { 91 | return false; 92 | } 93 | 94 | if (!Execute(_cloneStep, executionContext)) 95 | { 96 | return false; 97 | } 98 | 99 | if (!Execute(_installStep, executionContext)) 100 | { 101 | return false; 102 | } 103 | 104 | if (!Execute(_assemblyInfoStep, executionContext)) 105 | { 106 | return false; 107 | } 108 | 109 | // Before build 110 | if (!Execute(_beforeBuildStep, executionContext)) 111 | { 112 | return false; 113 | } 114 | 115 | // Build 116 | if (executionContext.BuildConfiguration.Build.IsAutomaticBuildOff) 117 | { 118 | if (!Execute(_buildScriptStep, executionContext)) 119 | { 120 | return false; 121 | } 122 | } 123 | else 124 | { 125 | if (!Execute(_buildStep, executionContext)) 126 | { 127 | return false; 128 | } 129 | } 130 | 131 | // After Build 132 | if (!Execute(_afterBuildStep, executionContext)) 133 | { 134 | return false; 135 | } 136 | 137 | // Test script 138 | if (!Execute(_testScriptStep, executionContext)) 139 | { 140 | return false; 141 | } 142 | 143 | return true; 144 | } 145 | 146 | private bool Execute(IEngineStep step, ExecutionContext executionContext) 147 | { 148 | if (executionContext.BuildConfiguration.SkipSteps.Contains(step.Name, StringComparer.InvariantCultureIgnoreCase)) 149 | { 150 | executionContext.Outputter.Write($"Skipped '{step.Name}' step."); 151 | return true; 152 | } 153 | 154 | return step.Execute(executionContext); 155 | } 156 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/ExecutionContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Abstractions; 3 | using LocalAppVeyor.Engine.Configuration; 4 | 5 | namespace LocalAppVeyor.Engine.Internal; 6 | 7 | internal class ExecutionContext 8 | { 9 | public MatrixJob CurrentJob { get; } 10 | 11 | public string RepositoryDirectory { get; } 12 | 13 | public ExpandableString CloneDirectory { get; } 14 | 15 | public IFileSystem FileSystem { get; } 16 | 17 | public BuildConfiguration BuildConfiguration { get; } 18 | 19 | public IPipelineOutputter Outputter { get; } 20 | 21 | public ExecutionContext( 22 | MatrixJob currentJob, 23 | BuildConfiguration buildConfiguration, 24 | IPipelineOutputter outputter, 25 | string repositoryDirectory, 26 | ExpandableString cloneDirectory, 27 | IFileSystem fileSystem) 28 | { 29 | CurrentJob = currentJob ?? throw new ArgumentNullException(nameof(currentJob)); 30 | BuildConfiguration = buildConfiguration ?? throw new ArgumentNullException(nameof(buildConfiguration)); 31 | Outputter = outputter ?? throw new ArgumentNullException(nameof(outputter)); 32 | RepositoryDirectory = repositoryDirectory ?? throw new ArgumentNullException(nameof(repositoryDirectory)); 33 | CloneDirectory = cloneDirectory; 34 | FileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); 35 | } 36 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/IEngineStep.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine.Internal; 2 | 3 | internal interface IEngineStep 4 | { 5 | string Name { get; } 6 | 7 | bool Execute(ExecutionContext executionContext); 8 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/KnownExceptions/SolutionNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.KnownExceptions; 4 | 5 | public class SolutionNotFoundException : Exception 6 | { 7 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/PipelineOutputterMsBuildLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LocalAppVeyor.Engine.Configuration; 3 | using Microsoft.Build.Framework; 4 | using Microsoft.Build.Logging; 5 | 6 | namespace LocalAppVeyor.Engine.Internal; 7 | 8 | internal class PipelineOutputterMsBuildLogger : ConsoleLogger 9 | { 10 | public PipelineOutputterMsBuildLogger(BuildVerbosity verbosity, IPipelineOutputter outputter) 11 | : base( 12 | TransformToLoggerVerbosity(verbosity), 13 | outputter.Write, 14 | outputter.SetColor, 15 | outputter.ResetColor 16 | ) 17 | { 18 | } 19 | 20 | private static LoggerVerbosity TransformToLoggerVerbosity(BuildVerbosity verbosity) 21 | { 22 | switch (verbosity) 23 | { 24 | case BuildVerbosity.Quiet: 25 | return LoggerVerbosity.Quiet; 26 | case BuildVerbosity.Minimal: 27 | return LoggerVerbosity.Minimal; 28 | case BuildVerbosity.Normal: 29 | return LoggerVerbosity.Normal; 30 | case BuildVerbosity.Detailed: 31 | return LoggerVerbosity.Detailed; 32 | default: 33 | throw new ArgumentOutOfRangeException(nameof(verbosity), verbosity, null); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Platform.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace LocalAppVeyor.Engine.Internal; 4 | 5 | public static class Platform 6 | { 7 | public static bool IsWindow { get; } 8 | 9 | public static bool IsUnix { get; } 10 | 11 | static Platform() 12 | { 13 | IsUnix = Path.DirectorySeparatorChar == '/'; 14 | IsWindow = !IsUnix; 15 | } 16 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/PowerShellScriptExecuter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Management.Automation; 3 | 4 | namespace LocalAppVeyor.Engine.Internal; 5 | 6 | internal static class PowerShellScriptExecuter 7 | { 8 | public static bool Execute( 9 | string script, 10 | Action onOutputDataReceived, 11 | Action onErrorDataReceived) 12 | { 13 | using var powerShell = PowerShell.Create(); 14 | var success = true; 15 | 16 | powerShell.AddScript(script); 17 | 18 | powerShell.Streams.Information.DataAdded += (sender, args) => 19 | { 20 | onOutputDataReceived(powerShell.Streams.Information[args.Index]); 21 | }; 22 | 23 | powerShell.Streams.Error.DataAdded += (sender, args) => 24 | { 25 | onErrorDataReceived(powerShell.Streams.Error[args.Index]); 26 | success = false; 27 | }; 28 | 29 | powerShell.Invoke(); 30 | 31 | return success; 32 | } 33 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/AfterBuildStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class AfterBuildStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "after_build"; 8 | 9 | public AfterBuildStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/AssemblyInfoRewriteStep.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace LocalAppVeyor.Engine.Internal.Steps; 5 | 6 | internal class AssemblyInfoRewriteStep : IEngineStep 7 | { 8 | public string Name => "assembly_info"; 9 | 10 | private static readonly Regex AssemblyVersionPattern = new Regex(@"AssemblyVersion\("".+""\)", RegexOptions.Compiled); 11 | 12 | private static readonly Regex AssemblyFileVersionPattern = new Regex(@"AssemblyFileVersion\("".+""\)", RegexOptions.Compiled); 13 | 14 | private static readonly Regex AssemblyInformationalVersionPattern = new Regex(@"AssemblyInformationalVersion\("".+""\)", RegexOptions.Compiled); 15 | 16 | public bool Execute(ExecutionContext executionContext) 17 | { 18 | if (!executionContext.BuildConfiguration.AssemblyInfo.Patch) 19 | { 20 | return true; 21 | } 22 | 23 | if (string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.File)) 24 | { 25 | executionContext.Outputter.WriteWarning("No assembly info files specified."); 26 | return true; 27 | } 28 | 29 | if (string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyFileVersion) && 30 | string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyVersion) && 31 | string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyInformationalVersion)) 32 | { 33 | executionContext.Outputter.WriteWarning("No versioning information provided to re-write AssemlyInfo files with."); 34 | return false; 35 | } 36 | 37 | foreach (var assemblyInfoFile in executionContext.FileSystem.Directory.EnumerateFiles( 38 | executionContext.CloneDirectory, 39 | executionContext.BuildConfiguration.AssemblyInfo.File, 40 | SearchOption.AllDirectories)) 41 | { 42 | if (!RewriteAssemblyInfoFile(executionContext, assemblyInfoFile)) 43 | { 44 | return false; 45 | } 46 | } 47 | 48 | return true; 49 | } 50 | 51 | private bool RewriteAssemblyInfoFile(ExecutionContext executionContext, string filePath) 52 | { 53 | executionContext.Outputter.Write($"Re-writing '{filePath}'..."); 54 | 55 | var fileContent = executionContext.FileSystem.File.ReadAllText(filePath); 56 | 57 | if (!string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyVersion)) 58 | { 59 | fileContent = AssemblyVersionPattern.Replace( 60 | fileContent, 61 | $@"AssemblyVersion(""{executionContext.BuildConfiguration.AssemblyInfo.AssemblyVersion}"")"); 62 | } 63 | 64 | if (!string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyFileVersion)) 65 | { 66 | fileContent = AssemblyFileVersionPattern.Replace( 67 | fileContent, 68 | $@"AssemblyFileVersion(""{executionContext.BuildConfiguration.AssemblyInfo.AssemblyFileVersion}"")"); 69 | } 70 | 71 | if (!string.IsNullOrEmpty(executionContext.BuildConfiguration.AssemblyInfo.AssemblyInformationalVersion)) 72 | { 73 | fileContent = AssemblyInformationalVersionPattern.Replace( 74 | fileContent, 75 | $@"AssemblyInformationalVersion(""{executionContext.BuildConfiguration.AssemblyInfo.AssemblyInformationalVersion}"")"); 76 | } 77 | 78 | executionContext.FileSystem.File.WriteAllText(filePath, fileContent); 79 | 80 | return true; 81 | } 82 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/BeforeBuildStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class BeforeBuildStep : ScriptBlockExecuterStep 6 | { 7 | public BeforeBuildStep(string workigDirectory, ScriptBlock scriptBlock) 8 | : base(workigDirectory, scriptBlock) 9 | { 10 | } 11 | 12 | public override string Name => "before_build"; 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/BuildScriptStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class BuildScriptStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "build_script"; 8 | 9 | public BuildScriptStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/BuildStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LocalAppVeyor.Engine.Internal.KnownExceptions; 5 | using Microsoft.Build.Execution; 6 | using Microsoft.Build.Framework; 7 | 8 | namespace LocalAppVeyor.Engine.Internal.Steps; 9 | 10 | internal class BuildStep : IEngineStep 11 | { 12 | public string Name => "build"; 13 | 14 | public bool Execute(ExecutionContext executionContext) 15 | { 16 | var platform = executionContext.CurrentJob.Platform; 17 | var configuration = executionContext.CurrentJob.Configuration; 18 | string slnProjFile = null; 19 | 20 | if (executionContext.FileSystem.File.Exists(executionContext.BuildConfiguration.Build.SolutionFile)) 21 | { 22 | slnProjFile = executionContext.BuildConfiguration.Build.SolutionFile; 23 | } 24 | else if (!string.IsNullOrEmpty(executionContext.BuildConfiguration.Build.SolutionFile)) 25 | { 26 | if ( 27 | !executionContext.FileSystem.File.Exists( 28 | slnProjFile = 29 | executionContext.FileSystem.Path.Combine(executionContext.CloneDirectory, 30 | executionContext.BuildConfiguration.Build.SolutionFile))) 31 | { 32 | slnProjFile = null; 33 | } 34 | } 35 | 36 | if (string.IsNullOrEmpty(slnProjFile)) 37 | { 38 | slnProjFile = GetProjectOrSolutionFileRecursively(executionContext); 39 | 40 | if (string.IsNullOrEmpty(slnProjFile)) 41 | { 42 | throw new SolutionNotFoundException(); 43 | } 44 | } 45 | 46 | // MSBuild 47 | 48 | var globalProperties = new Dictionary(); 49 | 50 | if (!string.IsNullOrEmpty(platform)) 51 | { 52 | globalProperties.Add("Platform", platform); 53 | } 54 | 55 | if (!string.IsNullOrEmpty(configuration)) 56 | { 57 | globalProperties.Add("Configuration", configuration); 58 | } 59 | 60 | var buildRequest = new BuildRequestData(slnProjFile, globalProperties, null, new[] {"Build"}, null); 61 | var buildParameters = new BuildParameters 62 | { 63 | Loggers = new ILogger[] 64 | { 65 | new PipelineOutputterMsBuildLogger( 66 | executionContext.BuildConfiguration.Build.Verbosity, 67 | executionContext.Outputter) 68 | } 69 | }; 70 | 71 | var buildResult = BuildManager.DefaultBuildManager.Build(buildParameters, buildRequest); 72 | 73 | return buildResult.OverallResult == BuildResultCode.Success; 74 | } 75 | 76 | private string GetProjectOrSolutionFileRecursively(ExecutionContext executionContext) 77 | { 78 | // first tries .sln file 79 | var possibleHit = executionContext.FileSystem.Directory 80 | .EnumerateFiles(executionContext.CloneDirectory, "*.sln") 81 | .FirstOrDefault(f => f.EndsWith("*.sln", StringComparison.OrdinalIgnoreCase)); 82 | 83 | if (!string.IsNullOrEmpty(possibleHit)) 84 | { 85 | return possibleHit; 86 | } 87 | 88 | // finally tries .csproj files 89 | return executionContext.FileSystem 90 | .Directory 91 | .EnumerateFiles(executionContext.CloneDirectory, "*.csproj") 92 | .FirstOrDefault(f => f.EndsWith("*.csproj", StringComparison.OrdinalIgnoreCase)); 93 | } 94 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/CloneFolderStep.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal class CloneFolderStep : IEngineStep 6 | { 7 | public string Name => "clone_folder"; 8 | 9 | private readonly IFileSystem _fileSystem; 10 | 11 | public CloneFolderStep(IFileSystem fileSystem) 12 | { 13 | _fileSystem = fileSystem; 14 | } 15 | 16 | public bool Execute(ExecutionContext executionContext) 17 | { 18 | executionContext.Outputter.Write($"Cloning '{executionContext.RepositoryDirectory}' in to '{executionContext.CloneDirectory}'..."); 19 | Clone(executionContext.RepositoryDirectory, executionContext.CloneDirectory); 20 | executionContext.Outputter.Write("Cloning finished."); 21 | 22 | return true; 23 | } 24 | 25 | private void Clone(string source, string destination) 26 | { 27 | var dirSource = _fileSystem.DirectoryInfo.New(source); 28 | var dirDestination = _fileSystem.DirectoryInfo.New(destination); 29 | 30 | if (!dirDestination.Exists) 31 | { 32 | _fileSystem.Directory.CreateDirectory(dirDestination.FullName); 33 | } 34 | 35 | // empty destination 36 | foreach (var fileInfo in dirDestination.GetFiles()) 37 | { 38 | fileInfo.Delete(); 39 | } 40 | 41 | foreach (var directoryInfo in dirDestination.GetDirectories()) 42 | { 43 | directoryInfo.Delete(true); 44 | } 45 | 46 | CopyAll(dirSource, dirDestination); 47 | } 48 | 49 | private void CopyAll(IDirectoryInfo source, IDirectoryInfo destination) 50 | { 51 | // copy each file into destination 52 | foreach (var file in source.GetFiles()) 53 | { 54 | file.CopyTo(_fileSystem.Path.Combine(destination.FullName, file.Name), true); 55 | } 56 | 57 | // copy each subdirectory using recursion 58 | foreach (var diSourceSubDir in source.GetDirectories()) 59 | { 60 | var nextTargetSubDir = destination.CreateSubdirectory(diSourceSubDir.Name); 61 | CopyAll(diSourceSubDir, nextTargetSubDir); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/InitStandardEnvironmentVariablesStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace LocalAppVeyor.Engine.Internal.Steps; 5 | 6 | internal class InitStandardEnvironmentVariablesStep : IEngineStep 7 | { 8 | public string Name => "environment"; 9 | 10 | public bool Execute(ExecutionContext executionContext) 11 | { 12 | executionContext.Outputter.Write("Initializing environment variables..."); 13 | 14 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_NUMBER", "0"); 15 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_VERSION", executionContext.BuildConfiguration.Version); 16 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_FOLDER", executionContext.CloneDirectory); 17 | Environment.SetEnvironmentVariable("CI", "False"); 18 | Environment.SetEnvironmentVariable("APPVEYOR", "False"); 19 | 20 | if (!string.IsNullOrEmpty(executionContext.CurrentJob.Configuration)) 21 | { 22 | Environment.SetEnvironmentVariable("CONFIGURATION", executionContext.CurrentJob.Configuration); 23 | } 24 | 25 | if (!string.IsNullOrEmpty(executionContext.CurrentJob.Platform)) 26 | { 27 | Environment.SetEnvironmentVariable("PLATFORM", executionContext.CurrentJob.Platform); 28 | } 29 | 30 | foreach ( 31 | var variable 32 | in executionContext.BuildConfiguration.EnvironmentVariables.CommonVariables.Concat(executionContext.CurrentJob.Variables)) 33 | { 34 | Environment.SetEnvironmentVariable(variable.Name, variable.Value); 35 | } 36 | 37 | executionContext.Outputter.Write("Environment variables initialized."); 38 | 39 | return true; 40 | } 41 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/InitStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal class InitStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "init"; 8 | 9 | public InitStep(string workingDirectory, ScriptBlock scriptBlock) 10 | : base(workingDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/InstallStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal class InstallStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "install"; 8 | 9 | public InstallStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/OnFailureStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class OnFailureStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "on_failure"; 8 | 9 | public OnFailureStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/OnFinishStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class OnFinishStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "on_finish"; 8 | 9 | public OnFinishStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/OnSuccessStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class OnSuccessStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "on_success"; 8 | 9 | public OnSuccessStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/ScriptBlockExecuterStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal abstract class ScriptBlockExecuterStep : IEngineStep 6 | { 7 | public abstract string Name { get; } 8 | 9 | private readonly ScriptBlock _scriptBlock; 10 | 11 | private readonly string _workingDirectory; 12 | 13 | protected ScriptBlockExecuterStep( 14 | string workingDirectory, 15 | ScriptBlock scriptBlock) 16 | { 17 | _scriptBlock = scriptBlock; 18 | _workingDirectory = workingDirectory; 19 | } 20 | 21 | public bool Execute(ExecutionContext executionContext) 22 | { 23 | if (_scriptBlock != null) 24 | { 25 | foreach (var scriptLine in _scriptBlock) 26 | { 27 | if (string.IsNullOrEmpty(scriptLine.Script)) 28 | { 29 | continue; 30 | } 31 | 32 | if (scriptLine.ScriptType == ScriptType.Bash && Platform.IsWindow) 33 | { 34 | executionContext.Outputter.Write("Script line skipped - Bash (sh) scripts cannot be executed in Windows platforms."); 35 | continue; 36 | } 37 | 38 | if (scriptLine.ScriptType == ScriptType.Batch && Platform.IsUnix) 39 | { 40 | executionContext.Outputter.Write("Script line skipped - Batch (cmd) scripts cannot be executed in Unix platforms."); 41 | continue; 42 | } 43 | 44 | var status = true; 45 | 46 | switch (scriptLine.ScriptType) 47 | { 48 | case ScriptType.Batch: 49 | status = ExecuteBatchScript(executionContext, scriptLine.Script); 50 | break; 51 | case ScriptType.PowerShell: 52 | status = ExecutePowerShellScript(executionContext, scriptLine.Script); 53 | break; 54 | case ScriptType.Bash: 55 | status = ExecuteBashScript(executionContext, scriptLine.Script); 56 | break; 57 | } 58 | 59 | if (!status) 60 | { 61 | return false; 62 | } 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | 69 | private bool ExecuteBatchScript(ExecutionContext executionContext, string script) 70 | { 71 | return BatchScriptExecuter.Execute( 72 | executionContext.FileSystem, 73 | _workingDirectory, 74 | script, 75 | data => 76 | { 77 | if (data != null) 78 | { 79 | executionContext.Outputter.Write(data); 80 | } 81 | }, 82 | data => 83 | { 84 | if (data != null) 85 | { 86 | executionContext.Outputter.WriteError(data); 87 | } 88 | }); 89 | } 90 | 91 | private static bool ExecutePowerShellScript(ExecutionContext executionContext, string script) 92 | { 93 | return PowerShellScriptExecuter.Execute( 94 | script, 95 | data => 96 | { 97 | if (data != null) 98 | { 99 | executionContext.Outputter.Write(data?.ToString() ?? "PowerShell: "); 100 | } 101 | }, 102 | data => 103 | { 104 | if (data != null) 105 | { 106 | executionContext.Outputter.WriteError(data?.ToString() ?? "PowerShell: Error while executing command."); 107 | } 108 | }); 109 | } 110 | 111 | private static bool ExecuteBashScript(ExecutionContext executionContext, string script) 112 | { 113 | return BashScriptExecuter.Execute( 114 | script, 115 | data => 116 | { 117 | if (data != null) 118 | { 119 | executionContext.Outputter.Write(data); 120 | } 121 | }, 122 | data => 123 | { 124 | if (data != null) 125 | { 126 | executionContext.Outputter.WriteError(data); 127 | } 128 | }); 129 | } 130 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Internal/Steps/TestScriptStep.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | 3 | namespace LocalAppVeyor.Engine.Internal.Steps; 4 | 5 | internal sealed class TestScriptStep : ScriptBlockExecuterStep 6 | { 7 | public override string Name => "test_script"; 8 | 9 | public TestScriptStep(string workigDirectory, ScriptBlock scriptBlock) 10 | : base(workigDirectory, scriptBlock) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/JobEndedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine; 4 | 5 | public sealed class JobEndedEventArgs : EventArgs 6 | { 7 | public MatrixJob Job { get; } 8 | 9 | public JobExecutionResult ExecutionResult { get; } 10 | 11 | public JobEndedEventArgs(MatrixJob job, JobExecutionResult executionResult) 12 | { 13 | Job = job; 14 | ExecutionResult = executionResult; 15 | } 16 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/JobExecutionResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine; 4 | 5 | public sealed class JobExecutionResult 6 | { 7 | public JobExecutionResultType ResultType { get; private set; } 8 | 9 | public bool IsSuccessfulExecution => ResultType == JobExecutionResultType.Success; 10 | 11 | public Exception UnhandledException { get; private set; } 12 | 13 | private JobExecutionResult () 14 | { 15 | } 16 | 17 | internal static JobExecutionResult CreateSuccess() 18 | { 19 | return new JobExecutionResult 20 | { 21 | ResultType = JobExecutionResultType.Success 22 | }; 23 | } 24 | 25 | internal static JobExecutionResult CreateFailure() 26 | { 27 | return new JobExecutionResult 28 | { 29 | ResultType = JobExecutionResultType.Failure 30 | }; 31 | } 32 | 33 | internal static JobExecutionResult CreateNotExecuted() 34 | { 35 | return new JobExecutionResult 36 | { 37 | ResultType = JobExecutionResultType.NotExecuted 38 | }; 39 | } 40 | 41 | internal static JobExecutionResult CreateUnhandledException(Exception exception) 42 | { 43 | return new JobExecutionResult 44 | { 45 | ResultType = JobExecutionResultType.UnhandledException, 46 | UnhandledException = exception 47 | }; 48 | } 49 | 50 | internal static JobExecutionResult CreateSolutionNotFound() 51 | { 52 | return new JobExecutionResult 53 | { 54 | ResultType = JobExecutionResultType.SolutionFileNotFound 55 | }; 56 | } 57 | 58 | internal static JobExecutionResult CreateJobNotFound() 59 | { 60 | return new JobExecutionResult 61 | { 62 | ResultType = JobExecutionResultType.JobNotFound 63 | }; 64 | } 65 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/JobExecutionResultType.cs: -------------------------------------------------------------------------------- 1 | namespace LocalAppVeyor.Engine; 2 | 3 | public enum JobExecutionResultType 4 | { 5 | Success, 6 | Failure, 7 | 8 | NotExecuted, 9 | JobNotFound, 10 | SolutionFileNotFound, 11 | UnhandledException 12 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/JobStartingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LocalAppVeyor.Engine; 4 | 5 | public sealed class JobStartingEventArgs : EventArgs 6 | { 7 | public MatrixJob Job { get; } 8 | 9 | public JobStartingEventArgs(MatrixJob job) 10 | { 11 | Job = job; 12 | } 13 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/LocalAppVeyor.Engine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Allows one to execute AppVeyor build pipeline programatically, for a given local repository and configuration. 5 | joaope 2020 6 | LocalAppVeyor.Engine 7 | 0.6.0 8 | joaope 9 | net8.0 10 | true 11 | anycpu 12 | latest 13 | portable 14 | LocalAppVeyor.Engine 15 | LocalAppVeyor.Engine 16 | console;appveyor;local;build;api 17 | https://github.com/joaope/LocalAppVeyor 18 | MIT 19 | git 20 | https://github.com/joaope/LocalAppVeyor.git 21 | NU1605 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/LocalAppVeyorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using YamlDotNet.Core; 3 | 4 | namespace LocalAppVeyor.Engine; 5 | 6 | public class LocalAppVeyorException : Exception 7 | { 8 | public Mark Start { get; } 9 | 10 | public Mark End { get; } 11 | 12 | public LocalAppVeyorException() 13 | { 14 | } 15 | 16 | public LocalAppVeyorException(string message) 17 | : base(message) 18 | { 19 | } 20 | 21 | public LocalAppVeyorException(string message, Exception innerException) 22 | : base(message, innerException) 23 | { 24 | if (!(innerException is YamlException yamlEx)) 25 | { 26 | return; 27 | } 28 | 29 | Start = new Mark(yamlEx.Start.Line, yamlEx.Start.Column); 30 | End = new Mark(yamlEx.End.Line, yamlEx.End.Column); 31 | } 32 | 33 | public sealed class Mark 34 | { 35 | public int Line { get; } 36 | 37 | public int Column { get; } 38 | 39 | public Mark(int line, int column) 40 | { 41 | Line = line; 42 | Column = column; 43 | } 44 | 45 | public override string ToString() 46 | { 47 | return $"(Line: {Line}, Column: {Column})"; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/MatrixJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using LocalAppVeyor.Engine.Configuration; 4 | 5 | namespace LocalAppVeyor.Engine; 6 | 7 | public class MatrixJob 8 | { 9 | public string OperatingSystem { get; } 10 | 11 | public string Platform { get; } 12 | 13 | public string Configuration { get; } 14 | 15 | public IReadOnlyCollection Variables { get; } 16 | 17 | private string _name; 18 | 19 | public string Name 20 | { 21 | get 22 | { 23 | if (_name != null) 24 | { 25 | return _name; 26 | } 27 | 28 | if (string.IsNullOrEmpty(OperatingSystem) && 29 | string.IsNullOrEmpty(Platform) && 30 | string.IsNullOrEmpty(Configuration) && 31 | Variables.Count == 0) 32 | { 33 | return "Default Job"; 34 | } 35 | 36 | var nameParts = new List(); 37 | 38 | if (!string.IsNullOrEmpty(OperatingSystem)) 39 | { 40 | nameParts.Add($"OS: {OperatingSystem}"); 41 | } 42 | 43 | if (Variables.Count > 0) 44 | { 45 | nameParts.Add($"Environment: {string.Join(", ", Variables.Select(v => v.ToString()))}"); 46 | } 47 | 48 | if (!string.IsNullOrEmpty(Configuration)) 49 | { 50 | nameParts.Add($"Configuration: {Configuration}"); 51 | } 52 | 53 | if (!string.IsNullOrEmpty(Platform)) 54 | { 55 | nameParts.Add($"Platform: {Platform}"); 56 | } 57 | 58 | return _name = string.Join("; ", nameParts); 59 | } 60 | } 61 | 62 | public MatrixJob( 63 | string operatingSystem, 64 | IReadOnlyCollection variables, 65 | string configuration, 66 | string platform) 67 | { 68 | OperatingSystem = operatingSystem; 69 | Platform = platform; 70 | Configuration = configuration; 71 | Variables = variables ?? new Variable[0]; 72 | } 73 | 74 | public override string ToString() 75 | { 76 | return Name; 77 | } 78 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor.Engine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("LocalAppVeyor.Engine.UnitTests")] -------------------------------------------------------------------------------- /src/LocalAppVeyor/Commands/BuildConsoleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using LocalAppVeyor.Engine; 7 | using LocalAppVeyor.Engine.Configuration; 8 | using LocalAppVeyor.Engine.Configuration.Reader; 9 | using McMaster.Extensions.CommandLineUtils; 10 | 11 | namespace LocalAppVeyor.Commands; 12 | 13 | internal class BuildConsoleCommand : ConsoleCommand 14 | { 15 | public override string Name => "build"; 16 | 17 | protected override string Description => "Executes appveyor.yml's build jobs from specified repository directory"; 18 | 19 | private CommandOption _repositoryPathOption; 20 | 21 | private CommandOption _jobsIndexesOption; 22 | 23 | private CommandOption _skipStepsOptions; 24 | 25 | public BuildConsoleCommand(IPipelineOutputter outputter) 26 | : base(outputter) 27 | { 28 | } 29 | 30 | protected override void SetUpAdditionalCommandOptions(CommandLineApplication app) 31 | { 32 | _repositoryPathOption = app.Option( 33 | "-d|--dir", 34 | "Local repository directory where appveyor.yml sits. If not specified current directory is used", 35 | CommandOptionType.SingleValue); 36 | 37 | _jobsIndexesOption = app.Option( 38 | "-j|--job", 39 | "Job to build. You can specify multiple jobs. Use 'jobs' command to list them all", 40 | CommandOptionType.MultipleValue); 41 | 42 | _skipStepsOptions = app.Option( 43 | "-s|--skip", 44 | "Step to skip from the build pipeline step. You can specify multiple steps.", 45 | CommandOptionType.MultipleValue); 46 | } 47 | 48 | protected override Task OnExecute(CommandLineApplication app) 49 | { 50 | var engineConfiguration = TryGetEngineConfigurationOrTerminate(_repositoryPathOption.Value()); 51 | var buildConfiguration = TryGetBuildConfigurationOrTerminate(engineConfiguration.RepositoryDirectoryPath); 52 | 53 | var engine = new Engine.Engine( 54 | engineConfiguration, 55 | buildConfiguration); 56 | 57 | engine.JobStarting += (sender, args) => 58 | { 59 | Outputter.Write($"Starting '{args.Job.Name}'..."); 60 | }; 61 | 62 | engine.JobEnded += (sender, args) => 63 | { 64 | switch (args.ExecutionResult.ResultType) 65 | { 66 | case JobExecutionResultType.Success: 67 | Outputter.WriteSuccess($"Job '{args.Job.Name}' successfully executed."); 68 | break; 69 | case JobExecutionResultType.Failure: 70 | Outputter.WriteError($"Job '{args.Job.Name}' failed."); 71 | break; 72 | case JobExecutionResultType.NotExecuted: 73 | Outputter.WriteError($"Job '{args.Job.Name}' will not be executed."); 74 | break; 75 | case JobExecutionResultType.JobNotFound: 76 | Outputter.WriteError("Specified job index not found. Use 'jobs' command to list available jobs."); 77 | break; 78 | case JobExecutionResultType.SolutionFileNotFound: 79 | Outputter.WriteError("Solution was not found."); 80 | break; 81 | case JobExecutionResultType.UnhandledException: 82 | Outputter.WriteError($"Unhandled exception while executing '{args.Job.Name}': " + 83 | $"{args.ExecutionResult.UnhandledException.Message} {args.ExecutionResult.UnhandledException.StackTrace}"); 84 | break; 85 | default: 86 | throw new ArgumentOutOfRangeException(); 87 | } 88 | }; 89 | 90 | int[] jobs; 91 | 92 | try 93 | { 94 | jobs = _jobsIndexesOption 95 | .Values 96 | .Select(int.Parse) 97 | .ToArray(); 98 | } 99 | catch (Exception) 100 | { 101 | Outputter.WriteError("Job option receives a integer as input. Use 'jobs' command to list all available jobs."); 102 | return Task.FromResult(1); 103 | } 104 | 105 | var jobsResults = new List(); 106 | 107 | if (jobs.Length == 0) 108 | { 109 | jobsResults = engine.ExecuteAllJobs().ToList(); 110 | } 111 | else 112 | { 113 | foreach (var jobIndex in jobs) 114 | { 115 | jobsResults.Add(engine.ExecuteJob(jobIndex)); 116 | } 117 | } 118 | 119 | PrintFinalResults(jobsResults); 120 | return Task.FromResult(0); 121 | } 122 | 123 | private static string ToFinalResultsString(JobExecutionResultType jobResult) 124 | { 125 | switch (jobResult) 126 | { 127 | case JobExecutionResultType.Success: 128 | return "Succeeded"; 129 | case JobExecutionResultType.Failure: 130 | return "Failed"; 131 | case JobExecutionResultType.NotExecuted: 132 | return "Not executed"; 133 | case JobExecutionResultType.JobNotFound: 134 | return "Job not found"; 135 | case JobExecutionResultType.SolutionFileNotFound: 136 | return "No solution file"; 137 | case JobExecutionResultType.UnhandledException: 138 | return "Unhandled exception"; 139 | default: 140 | throw new ArgumentOutOfRangeException(nameof(jobResult), jobResult, null); 141 | } 142 | } 143 | 144 | private void PrintFinalResults(IReadOnlyCollection jobsResults) 145 | { 146 | if (jobsResults.Count == 0) 147 | { 148 | Outputter.Write("Execution finished."); 149 | return; 150 | } 151 | 152 | Outputter.Write("Execution finished:"); 153 | 154 | var groupedResults = jobsResults 155 | .GroupBy(r => r.ResultType) 156 | .Select(g => $"{ToFinalResultsString(g.Key)}: {g.Count()}"); 157 | 158 | Outputter.Write( 159 | $"=== Execution Result: {string.Join(", ", groupedResults)} ==="); 160 | } 161 | 162 | private BuildConfiguration TryGetBuildConfigurationOrTerminate(string repositoryPathStr) 163 | { 164 | var appVeyorYml = Path.Combine(repositoryPathStr, "appveyor.yml"); 165 | 166 | if (!File.Exists(appVeyorYml)) 167 | { 168 | Outputter.WriteError("appveyor.yml file not found on repository path. Trying '.appveyor.yml'..."); 169 | 170 | appVeyorYml = Path.Combine(repositoryPathStr, ".appveyor.yml"); 171 | 172 | if (!File.Exists(appVeyorYml)) 173 | { 174 | Outputter.WriteError(".appveyor.yml file not found on repository path. Build aborted."); 175 | Environment.Exit(1); 176 | } 177 | } 178 | 179 | BuildConfiguration configuration = null; 180 | 181 | try 182 | { 183 | configuration = new BuildConfigurationYamlFileReader(appVeyorYml) 184 | .GetBuildConfiguration(); 185 | } 186 | catch (LocalAppVeyorException exception) 187 | { 188 | Outputter.WriteError(GetDetailedErrorMessage(appVeyorYml, exception)); 189 | Environment.Exit(1); 190 | } 191 | 192 | if (_skipStepsOptions.Values != null) 193 | { 194 | configuration.SkipSteps = _skipStepsOptions.Values.ToArray(); 195 | } 196 | 197 | return configuration; 198 | } 199 | 200 | private static string GetDetailedErrorMessage(string appVeyorYml, LocalAppVeyorException exception) 201 | { 202 | string marksMessage = null; 203 | 204 | if (exception.Start != null && exception.End != null) 205 | { 206 | marksMessage = $"{exception.Start} to {exception.End}"; 207 | } 208 | 209 | return $"Error while parsing '{appVeyorYml}' file{(marksMessage != null ? $" {marksMessage}." : ".")}"; 210 | } 211 | 212 | private EngineConfiguration TryGetEngineConfigurationOrTerminate(string repositoryPath) 213 | { 214 | if (!string.IsNullOrEmpty(repositoryPath)) 215 | { 216 | if (!Directory.Exists(repositoryPath)) 217 | { 218 | Outputter.WriteError($"Repository directory '{repositoryPath}' not found. Build aborted."); 219 | Environment.Exit(1); 220 | } 221 | } 222 | else 223 | { 224 | repositoryPath = Directory.GetCurrentDirectory(); 225 | } 226 | 227 | return new EngineConfiguration(repositoryPath, Outputter); 228 | } 229 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/Commands/ConsoleCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using LocalAppVeyor.Engine; 3 | using McMaster.Extensions.CommandLineUtils; 4 | 5 | namespace LocalAppVeyor.Commands; 6 | 7 | internal abstract class ConsoleCommand 8 | { 9 | public abstract string Name { get; } 10 | 11 | protected abstract string Description { get; } 12 | 13 | protected IPipelineOutputter Outputter { get; } 14 | 15 | protected ConsoleCommand(IPipelineOutputter outputter) 16 | { 17 | Outputter = outputter; 18 | } 19 | 20 | public void SetUp(CommandLineApplication app) 21 | { 22 | app.Description = Description; 23 | app.HelpOption("-?|-h|--help"); 24 | 25 | SetUpAdditionalCommandOptions(app); 26 | 27 | app.OnExecuteAsync(token => OnExecute(app)); 28 | } 29 | 30 | protected virtual void SetUpAdditionalCommandOptions(CommandLineApplication app) 31 | { 32 | } 33 | 34 | protected abstract Task OnExecute(CommandLineApplication app); 35 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/Commands/JobsConsoleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using LocalAppVeyor.Engine; 5 | using LocalAppVeyor.Engine.Configuration; 6 | using LocalAppVeyor.Engine.Configuration.Reader; 7 | using McMaster.Extensions.CommandLineUtils; 8 | 9 | namespace LocalAppVeyor.Commands; 10 | 11 | internal class JobsConsoleCommand : ConsoleCommand 12 | { 13 | private CommandOption _repositoryPathOption; 14 | 15 | public override string Name => "jobs"; 16 | 17 | protected override string Description => "List all build jobs available to execution"; 18 | 19 | public JobsConsoleCommand(IPipelineOutputter outputter) 20 | : base(outputter) 21 | { 22 | } 23 | 24 | protected override void SetUpAdditionalCommandOptions(CommandLineApplication app) 25 | { 26 | _repositoryPathOption = app.Option( 27 | "-d|--dir", 28 | "Local repository directory where appveyor.yml sits. If not specified current directory is used", 29 | CommandOptionType.SingleValue); 30 | } 31 | 32 | protected override Task OnExecute(CommandLineApplication app) 33 | { 34 | var engineConfiguration = TryGetEngineConfigurationOrTerminate(_repositoryPathOption.Value()); 35 | var buildConfiguration = TryGetBuildConfigurationOrTerminate(engineConfiguration.RepositoryDirectoryPath); 36 | 37 | var engine = new Engine.Engine( 38 | engineConfiguration, 39 | buildConfiguration); 40 | 41 | engineConfiguration.Outputter.Write("Available jobs:"); 42 | for (var i = 0; i < engine.Jobs.Length; i++) 43 | { 44 | engineConfiguration.Outputter.Write( 45 | $"[{i}]: {engine.Jobs[i].Name}"); 46 | } 47 | 48 | return Task.FromResult(0); 49 | } 50 | 51 | private BuildConfiguration TryGetBuildConfigurationOrTerminate(string repositoryPathStr) 52 | { 53 | var appVeyorYml = Path.Combine(repositoryPathStr, "appveyor.yml"); 54 | 55 | if (!File.Exists(appVeyorYml)) 56 | { 57 | Outputter.Write("appveyor.yml file not found on repository path. Trying '.appveyor.yml'..."); 58 | 59 | appVeyorYml = Path.Combine(repositoryPathStr, ".appveyor.yml"); 60 | 61 | if (!File.Exists(appVeyorYml)) 62 | { 63 | Outputter.WriteError(".appveyor.yml file not found on repository path."); 64 | Environment.Exit(1); 65 | } 66 | } 67 | 68 | BuildConfiguration configuration = null; 69 | 70 | try 71 | { 72 | configuration = new BuildConfigurationYamlFileReader(appVeyorYml) 73 | .GetBuildConfiguration(); 74 | } 75 | catch (LocalAppVeyorException) 76 | { 77 | Outputter.WriteError($"Error while parsing '{appVeyorYml}' file."); 78 | Environment.Exit(1); 79 | } 80 | 81 | return configuration; 82 | } 83 | 84 | private EngineConfiguration TryGetEngineConfigurationOrTerminate(string repositoryPath) 85 | { 86 | if (!string.IsNullOrEmpty(repositoryPath)) 87 | { 88 | if (!Directory.Exists(repositoryPath)) 89 | { 90 | Outputter.WriteError($"Repository directory '{repositoryPath}' not found."); 91 | Environment.Exit(1); 92 | } 93 | } 94 | else 95 | { 96 | repositoryPath = Directory.GetCurrentDirectory(); 97 | } 98 | 99 | return new EngineConfiguration(repositoryPath, Outputter); 100 | } 101 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/Commands/LintConsoleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Security; 7 | using System.Text.Json; 8 | using System.Threading.Tasks; 9 | using LocalAppVeyor.Engine; 10 | using McMaster.Extensions.CommandLineUtils; 11 | 12 | namespace LocalAppVeyor.Commands; 13 | 14 | internal sealed class LintConsoleCommand : ConsoleCommand 15 | { 16 | private const string TokenEnvironmentVariableName = "LOCALAPPVEYOR_API_TOKEN"; 17 | 18 | private const string YmlValidationUrl = "https://ci.appveyor.com/api/projects/validate-yaml"; 19 | 20 | private CommandOption _repositoryPathOption; 21 | 22 | private CommandOption _apiTokenOption; 23 | 24 | public override string Name => "lint"; 25 | 26 | protected override string Description => "Validates appveyor.yml YAML configuration. It requires internet connection."; 27 | 28 | public LintConsoleCommand(IPipelineOutputter outputter) 29 | : base(outputter) 30 | { 31 | } 32 | 33 | protected override void SetUpAdditionalCommandOptions(CommandLineApplication app) 34 | { 35 | _apiTokenOption = app.Option( 36 | "-t|--token", 37 | $"Your AppVeyor account API token. If not specified it tries to get it from {TokenEnvironmentVariableName} " + 38 | "environment variable. You can find your API token here: https://ci.appveyor.com/api-token.", 39 | CommandOptionType.SingleValue); 40 | 41 | _repositoryPathOption = app.Option( 42 | "-d|--dir", 43 | "Local repository directory where appveyor.yml sits. If not specified current directory is used", 44 | CommandOptionType.SingleValue); 45 | } 46 | 47 | protected override async Task OnExecute(CommandLineApplication app) 48 | { 49 | var apiToken = _apiTokenOption.Value(); 50 | 51 | if (string.IsNullOrEmpty(apiToken)) 52 | { 53 | apiToken = Environment.GetEnvironmentVariable( 54 | TokenEnvironmentVariableName, 55 | EnvironmentVariableTarget.User); 56 | 57 | if (string.IsNullOrEmpty(apiToken)) 58 | { 59 | Outputter.WriteError("AppVeyor API token is required. Either specify it using --token " + 60 | $"option or {TokenEnvironmentVariableName} environment variable."); 61 | Environment.Exit(1); 62 | } 63 | } 64 | 65 | var repositoryPath = TryGetRepositoryDirectoryPathOrTerminate(_repositoryPathOption.Value()); 66 | var (yamlFilePath, ymlFileContent) = TryGetAppVeyorFileContentOrTerminate(repositoryPath); 67 | 68 | Outputter.Write($"Validating '{yamlFilePath}'..."); 69 | 70 | using var client = new HttpClient(); 71 | client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 72 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken); 73 | 74 | Outputter.Write("Connecting to AppVeyor validation API..."); 75 | 76 | try 77 | { 78 | using var response = await client.PostAsync(YmlValidationUrl, new StringContent(ymlFileContent)); 79 | var responseContent = await response.Content.ReadAsStringAsync(); 80 | var responseObj = JsonSerializer.Deserialize(responseContent); 81 | 82 | switch (response.StatusCode) 83 | { 84 | case HttpStatusCode.OK when (bool)responseObj.isValid: 85 | Outputter.WriteSuccess("YAML configuration file is valid."); 86 | return 0; 87 | case HttpStatusCode.OK: 88 | Outputter.WriteError((string)responseObj.errorMessage); 89 | break; 90 | case HttpStatusCode.Unauthorized: 91 | Outputter.WriteError("Authorization failed. Make sure you're specifying an updated API token."); 92 | break; 93 | default: 94 | var msg = (string) responseObj.message; 95 | Outputter.WriteError(!string.IsNullOrEmpty(msg) 96 | ? $"Validation failed with status code {response.StatusCode}. Message: {msg}" 97 | : $"Validation failed with status code {response.StatusCode}."); 98 | 99 | break; 100 | } 101 | } 102 | catch (HttpRequestException) 103 | { 104 | Outputter.WriteError("Error connecting to AppVeyor validation API. Check your interner connection."); 105 | } 106 | 107 | return 1; 108 | } 109 | 110 | private (string YamlFilePath, string YmlFileContent) TryGetAppVeyorFileContentOrTerminate(string repositoryPath) 111 | { 112 | var appVeyorYml = Path.Combine(repositoryPath, "appveyor.yml"); 113 | 114 | if (!File.Exists(appVeyorYml)) 115 | { 116 | Outputter.Write("appveyor.yml file not found on repository path. Trying '.appveyor.yml'..."); 117 | 118 | appVeyorYml = Path.Combine(repositoryPath, ".appveyor.yml"); 119 | 120 | if (!File.Exists(appVeyorYml)) 121 | { 122 | Outputter.WriteError(".appveyor.yml file not found on repository path. Validation stopped."); 123 | Environment.Exit(1); 124 | } 125 | } 126 | 127 | string exceptionReason; 128 | 129 | try 130 | { 131 | return (appVeyorYml, File.ReadAllText(appVeyorYml)); 132 | } 133 | catch (PathTooLongException) 134 | { 135 | exceptionReason = "Path too long"; 136 | } 137 | catch (DirectoryNotFoundException) 138 | { 139 | exceptionReason = "Directory not found"; 140 | } 141 | catch (FileNotFoundException) 142 | { 143 | exceptionReason = "File not found"; 144 | } 145 | catch (NotSupportedException) 146 | { 147 | exceptionReason = "Path is in an invalid format"; 148 | } 149 | catch (IOException e) 150 | { 151 | exceptionReason = e.Message; 152 | } 153 | catch (UnauthorizedAccessException) 154 | { 155 | exceptionReason = "No permissions to read configuration file"; 156 | } 157 | catch (SecurityException) 158 | { 159 | exceptionReason = "The caller does not have the required permission"; 160 | } 161 | 162 | Outputter.WriteError($"Error while trying to read '{appVeyorYml}' file. {exceptionReason}. Validation aborted."); 163 | Environment.Exit(1); 164 | return (null, null); 165 | } 166 | 167 | private string TryGetRepositoryDirectoryPathOrTerminate(string repositoryPath) 168 | { 169 | if (!string.IsNullOrEmpty(repositoryPath)) 170 | { 171 | if (!Directory.Exists(repositoryPath)) 172 | { 173 | Outputter.WriteError($"Repository directory '{repositoryPath}' not found. Validation aborted."); 174 | Environment.Exit(1); 175 | } 176 | 177 | return repositoryPath; 178 | } 179 | 180 | return Directory.GetCurrentDirectory(); 181 | } 182 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/ConsoleOutputter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LocalAppVeyor.Engine; 3 | 4 | namespace LocalAppVeyor; 5 | 6 | internal sealed class ConsoleOutputter : IPipelineOutputter 7 | { 8 | public void SetColor(ConsoleColor color) 9 | { 10 | Console.ForegroundColor = TransformColor(color, Console.BackgroundColor); 11 | } 12 | 13 | public void ResetColor() 14 | { 15 | Console.ResetColor(); 16 | } 17 | 18 | private static ConsoleColor TransformColor( 19 | ConsoleColor foreground, 20 | ConsoleColor background) 21 | { 22 | if (foreground != background) 23 | { 24 | return foreground; 25 | } 26 | 27 | return background != ConsoleColor.Black 28 | ? ConsoleColor.Black 29 | : ConsoleColor.Gray; 30 | } 31 | 32 | public void Write(string message) 33 | { 34 | Console.WriteLine($"{message ?? ""}"); 35 | } 36 | 37 | public void WriteSuccess(string successMessage) 38 | { 39 | SetColor(ConsoleColor.Green); 40 | Console.WriteLine($"{successMessage ?? ""}"); 41 | ResetColor(); 42 | } 43 | 44 | public void WriteWarning(string warningMessage) 45 | { 46 | SetColor(ConsoleColor.Yellow); 47 | Console.WriteLine($"{warningMessage ?? ""}"); 48 | ResetColor(); 49 | } 50 | 51 | public void WriteError(string errorMessage) 52 | { 53 | SetColor(ConsoleColor.Red); 54 | Console.WriteLine($"{errorMessage ?? ""}"); 55 | ResetColor(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/LocalAppVeyor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Run your AppVeyor builds, locally. 5 | joaope 2020 6 | 0.6.0 7 | joaope 8 | net8.0 9 | true 10 | latest 11 | Exe 12 | true 13 | true 14 | localappveyor 15 | localappveyor 16 | console;appveyor;local;build;api 17 | https://github.com/joaope/LocalAppVeyor 18 | MIT 19 | git 20 | https://github.com/joaope/LocalAppVeyor.git 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/LocalAppVeyor/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using LocalAppVeyor.Commands; 4 | using LocalAppVeyor.Engine; 5 | using McMaster.Extensions.CommandLineUtils; 6 | 7 | namespace LocalAppVeyor; 8 | 9 | public static class Program 10 | { 11 | private static readonly IPipelineOutputter PipelineOutputter = new ConsoleOutputter(); 12 | 13 | private static readonly BuildConsoleCommand BuildCommand = new BuildConsoleCommand(PipelineOutputter); 14 | 15 | private static readonly JobsConsoleCommand JobsCommand = new JobsConsoleCommand(PipelineOutputter); 16 | 17 | private static readonly LintConsoleCommand LintCommand = new LintConsoleCommand(PipelineOutputter); 18 | 19 | public static void Main(string[] args) 20 | { 21 | var app = new CommandLineApplication 22 | { 23 | Name = "LocalAppVeyor", 24 | FullName = "LocalAppVeyor", 25 | Description = "LocalAppVeyor allows one to run an appveyor.yml build script locally", 26 | UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.StopParsingAndCollect 27 | }; 28 | 29 | var (shortFormVersion, longFormVersion) = GetShortAndLongVersion(); 30 | 31 | app.HelpOption("-?|-h|--help"); 32 | app.VersionOption("-v|--version", shortFormVersion, longFormVersion); 33 | 34 | app.Command(BuildCommand.Name, conf => { BuildCommand.SetUp(conf); }); 35 | app.Command(JobsCommand.Name, conf => { JobsCommand.SetUp(conf); }); 36 | app.Command(LintCommand.Name, conf => { LintCommand.SetUp(conf); }); 37 | 38 | app.OnExecute(() => 39 | { 40 | app.ShowHelp(); 41 | return 0; 42 | }); 43 | 44 | app.Execute(args); 45 | } 46 | 47 | private static (string ShortFormVersion, string LongFormVersion) GetShortAndLongVersion() 48 | { 49 | string GetVersionFromTypeInfo(Type typeInfo) 50 | { 51 | var infoVersion = typeInfo.Assembly.GetCustomAttribute() 52 | .InformationalVersion; 53 | 54 | if (string.IsNullOrEmpty(infoVersion)) 55 | { 56 | infoVersion = typeInfo.Assembly.GetName().Version.ToString(); 57 | } 58 | 59 | return infoVersion; 60 | } 61 | 62 | var consoleVer = GetVersionFromTypeInfo(typeof(Program).GetTypeInfo()); 63 | var engineVer = GetVersionFromTypeInfo(typeof(Engine.Engine).GetTypeInfo()); 64 | 65 | return (consoleVer, $"{consoleVer} (engine: {engineVer})"); 66 | } 67 | } -------------------------------------------------------------------------------- /src/LocalAppVeyor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LocalAppVeyor": { 4 | "commandName": "Project" 5 | }, 6 | "LocalAppVeyor (jobs)": { 7 | "commandName": "Project", 8 | "commandLineArgs": "jobs" 9 | }, 10 | "LocalAppVeyor (build)": { 11 | "commandName": "Project", 12 | "commandLineArgs": "build" 13 | }, 14 | "LocalAppVeyor (lint)": { 15 | "commandName": "Project", 16 | "commandLineArgs": "lint" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/AssemblyInfoTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LocalAppVeyor.Engine.Configuration; 3 | using LocalAppVeyor.Engine.Configuration.Reader; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 7 | { 8 | public class AssemblyInfoTests 9 | { 10 | [Fact] 11 | public void ShouldReadAssemblyInfoStep() 12 | { 13 | const string yaml = @" 14 | assembly_info: 15 | patch: true 16 | file: AssemblyInfo.* 17 | assembly_version: ""2.2.{build}"" 18 | assembly_file_version: ""{version}"" 19 | assembly_informational_version: ""{version}"" 20 | "; 21 | 22 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 23 | 24 | conf.AssemblyInfo.Should().BeEquivalentTo(new AssemblyInfo( 25 | true, 26 | "AssemblyInfo.*", 27 | "2.2.{build}", 28 | "{version}", 29 | "{version}")); 30 | } 31 | 32 | [Fact] 33 | public void ShouldBePatchFalseForAssemblyInfoWhenNotSpecified() 34 | { 35 | var conf = new BuildConfigurationYamlStringReader(string.Empty).GetBuildConfiguration(); 36 | 37 | conf.AssemblyInfo.Should().BeEquivalentTo(new AssemblyInfo()); 38 | conf.AssemblyInfo.Should().BeEquivalentTo(new AssemblyInfo( 39 | false, 40 | null, 41 | null, 42 | null, 43 | null)); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/BuildScriptTests.Unix.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | using LocalAppVeyor.Engine.Configuration.Reader; 3 | using LocalAppVeyor.Engine.UnitTests.TestUtilities; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 7 | { 8 | public partial class BuildScriptTests 9 | { 10 | [UnixOnlyFact] 11 | public void Unix_ShouldReadBuildScriptAsAScriptBlockWithMultipleDifferentTypeScripts() 12 | { 13 | const string yaml = @" 14 | build_script: 15 | # by default, all script lines are interpreted as bash 16 | - echo This is bash 17 | # to run script as a PowerShell command prepend it with ps: 18 | - ps: Write-Host 'This is PowerShell' 19 | # bash commands start from cmd: 20 | - cmd: echo This is bash again 21 | - cmd: set MY_VAR=12345 22 | "; 23 | 24 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 25 | 26 | Assert.Equal(4, conf.BuildScript.Count); 27 | Assert.Equal("echo This is bash", conf.BuildScript[0].Script); 28 | Assert.Equal(ScriptType.Bash, conf.BuildScript[0].ScriptType); 29 | Assert.Equal("Write-Host 'This is PowerShell'", conf.BuildScript[1].Script); 30 | Assert.Equal(ScriptType.PowerShell, conf.BuildScript[1].ScriptType); 31 | Assert.Equal("echo This is bash again", conf.BuildScript[2].Script); 32 | Assert.Equal(ScriptType.Batch, conf.BuildScript[2].ScriptType); 33 | Assert.Equal("set MY_VAR=12345", conf.BuildScript[3].Script); 34 | Assert.Equal(ScriptType.Batch, conf.BuildScript[3].ScriptType); 35 | } 36 | 37 | [UnixOnlyFact] 38 | public void Unix_ShouldReadBuildScriptAsAScriptBlockWithSplittedLinesScripts() 39 | { 40 | const string yaml = @" 41 | build_script: 42 | - |- 43 | echo -------------------------------------------------------------------------------- 44 | echo Build tinyformat 45 | mkdir build 46 | cd build 47 | cmake -G ""%COMPILER%"" .. 48 | cmake --build . --config %CONFIGURATION% 49 | "; 50 | 51 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 52 | 53 | Assert.Single(conf.BuildScript); 54 | Assert.Equal("echo --------------------------------------------------------------------------------\n" + 55 | "echo Build tinyformat\n" + 56 | "mkdir build\n" + 57 | "cd build\n" + 58 | "cmake -G \"%COMPILER%\" ..\n" + 59 | "cmake --build . --config %CONFIGURATION%", 60 | conf.BuildScript[0].Script); 61 | Assert.Equal(ScriptType.Bash, conf.BuildScript[0].ScriptType); 62 | } 63 | 64 | [UnixOnlyFact] 65 | public void Unix_ShouldReadBuildScriptAsAScriptBlockWithSplittedLinesScripts_AlternativeBlockStyle() 66 | { 67 | const string yaml = @" 68 | build_script: 69 | - sh: |- 70 | echo -------------------------------------------------------------------------------- 71 | echo Build tinyformat 72 | mkdir build 73 | cd build 74 | cmake -G ""%COMPILER%"" .. 75 | cmake --build . --config %CONFIGURATION% 76 | "; 77 | 78 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 79 | 80 | Assert.Single(conf.BuildScript); 81 | Assert.Equal("echo --------------------------------------------------------------------------------\n" + 82 | "echo Build tinyformat\n" + 83 | "mkdir build\n" + 84 | "cd build\n" + 85 | "cmake -G \"%COMPILER%\" ..\n" + 86 | "cmake --build . --config %CONFIGURATION%", 87 | conf.BuildScript[0].Script); 88 | Assert.Equal(ScriptType.Bash, conf.BuildScript[0].ScriptType); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/BuildScriptTests.Window.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | using LocalAppVeyor.Engine.Configuration.Reader; 3 | using LocalAppVeyor.Engine.UnitTests.TestUtilities; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 7 | { 8 | public partial class BuildScriptTests 9 | { 10 | [WindowsOnlyFact] 11 | public void Windows_ShouldReadBuildScriptAsAScriptBlockWithMultipleDifferentTypeScripts() 12 | { 13 | const string yaml = @" 14 | build_script: 15 | # by default, all script lines are interpreted as batch 16 | - echo This is batch 17 | # to run script as a PowerShell command prepend it with ps: 18 | - ps: Write-Host 'This is PowerShell' 19 | # batch commands start from cmd: 20 | - cmd: echo This is batch again 21 | - cmd: set MY_VAR=12345 22 | "; 23 | 24 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 25 | 26 | Assert.Equal(4, conf.BuildScript.Count); 27 | Assert.Equal("echo This is batch", conf.BuildScript[0].Script); 28 | Assert.Equal(ScriptType.Batch, conf.BuildScript[0].ScriptType); 29 | Assert.Equal("Write-Host 'This is PowerShell'", conf.BuildScript[1].Script); 30 | Assert.Equal(ScriptType.PowerShell, conf.BuildScript[1].ScriptType); 31 | Assert.Equal("echo This is batch again", conf.BuildScript[2].Script); 32 | Assert.Equal(ScriptType.Batch, conf.BuildScript[2].ScriptType); 33 | Assert.Equal("set MY_VAR=12345", conf.BuildScript[3].Script); 34 | Assert.Equal(ScriptType.Batch, conf.BuildScript[3].ScriptType); 35 | } 36 | 37 | [WindowsOnlyFact] 38 | public void Windows_ShouldReadBuildScriptAsAScriptBlockWithSplittedLinesScripts() 39 | { 40 | const string yaml = @" 41 | build_script: 42 | - |- 43 | echo -------------------------------------------------------------------------------- 44 | echo Build tinyformat 45 | mkdir build 46 | cd build 47 | cmake -G ""%COMPILER%"" .. 48 | cmake --build . --config %CONFIGURATION% 49 | "; 50 | 51 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 52 | 53 | Assert.Single(conf.BuildScript); 54 | Assert.Equal("echo --------------------------------------------------------------------------------\n" + 55 | "echo Build tinyformat\n" + 56 | "mkdir build\n" + 57 | "cd build\n" + 58 | "cmake -G \"%COMPILER%\" ..\n" + 59 | "cmake --build . --config %CONFIGURATION%", 60 | conf.BuildScript[0].Script); 61 | Assert.Equal(ScriptType.Batch, conf.BuildScript[0].ScriptType); 62 | } 63 | 64 | [WindowsOnlyFact] 65 | public void Windows_ShouldReadBuildScriptAsAScriptBlockWithSplittedLinesScripts_AlternativeBlockStyle() 66 | { 67 | const string yaml = @" 68 | build_script: 69 | - cmd: |- 70 | echo -------------------------------------------------------------------------------- 71 | echo Build tinyformat 72 | mkdir build 73 | cd build 74 | cmake -G ""%COMPILER%"" .. 75 | cmake --build . --config %CONFIGURATION% 76 | "; 77 | 78 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 79 | 80 | Assert.Single(conf.BuildScript); 81 | Assert.Equal("echo --------------------------------------------------------------------------------\n" + 82 | "echo Build tinyformat\n" + 83 | "mkdir build\n" + 84 | "cd build\n" + 85 | "cmake -G \"%COMPILER%\" ..\n" + 86 | "cmake --build . --config %CONFIGURATION%", 87 | conf.BuildScript[0].Script); 88 | Assert.Equal(ScriptType.Batch, conf.BuildScript[0].ScriptType); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/BuildScriptTests.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | using LocalAppVeyor.Engine.Configuration.Reader; 3 | using Xunit; 4 | 5 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 6 | { 7 | public partial class BuildScriptTests 8 | { 9 | [Fact] 10 | public void ShouldReadBuildScriptAsAScriptBlockWithSplittedLinesScripts_AlternativeBlockStylePowershell() 11 | { 12 | const string yaml = @" 13 | build_script: 14 | - ps: |- 15 | echo -------------------------------------------------------------------------------- 16 | echo Build tinyformat 17 | mkdir build 18 | cd build 19 | cmake -G ""%COMPILER%"" .. 20 | cmake --build . --config %CONFIGURATION% 21 | "; 22 | 23 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 24 | 25 | Assert.Single(conf.BuildScript); 26 | Assert.Equal("echo --------------------------------------------------------------------------------\n" + 27 | "echo Build tinyformat\n" + 28 | "mkdir build\n" + 29 | "cd build\n" + 30 | "cmake -G \"%COMPILER%\" ..\n" + 31 | "cmake --build . --config %CONFIGURATION%", 32 | conf.BuildScript[0].Script); 33 | Assert.Equal(ScriptType.PowerShell, conf.BuildScript[0].ScriptType); 34 | } 35 | 36 | [Fact] 37 | public void ShouldGracefullyReadAnInvalidScriptBlock_AndMakeItAnEmptyScript() 38 | { 39 | const string yaml = @" 40 | build_script: true; 41 | "; 42 | 43 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 44 | 45 | Assert.Empty(conf.BuildScript); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/EnvironmentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using FluentAssertions; 5 | using LocalAppVeyor.Engine.Configuration; 6 | using LocalAppVeyor.Engine.Configuration.Reader; 7 | using Xunit; 8 | 9 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 10 | { 11 | public class EnvironmentTests 12 | { 13 | [Fact] 14 | public void ShouldReadEnvironmentWithCommonAndMatrixVariables() 15 | { 16 | const string yaml = @" 17 | environment: 18 | common_var1: common_value1 19 | common_var2: 20 | secure: common_value2_secured 21 | matrix: 22 | - db: mysql 23 | password: mysql_password 24 | - db: sqlserver 25 | password: 26 | secure: sqlserver_secured_password 27 | "; 28 | 29 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 30 | 31 | conf.EnvironmentVariables.Should().BeEquivalentTo( 32 | new EnvironmentVariables( 33 | new List 34 | { 35 | new Variable("common_var1", "common_value1", false), 36 | new Variable("common_var2", "common_value2_secured", true) 37 | }, 38 | new List> 39 | { 40 | new ReadOnlyCollection(new List 41 | { 42 | new Variable("db", "mysql", false), 43 | new Variable("password", "mysql_password", false) 44 | }), 45 | new ReadOnlyCollection(new List 46 | { 47 | new Variable("db", "sqlserver", false), 48 | new Variable("password", "sqlserver_secured_password", true) 49 | }) 50 | })); 51 | } 52 | 53 | [Fact] 54 | public void ShouldReadCommonVariablesInsideGlobal() 55 | { 56 | const string yaml = @" 57 | environment: 58 | global: 59 | common_var1: common_value1 60 | common_var2: 61 | secure: common_value2_secured 62 | matrix: 63 | - db: mysql 64 | password: mysql_password 65 | - db: sqlserver 66 | password: 67 | secure: sqlserver_secured_password 68 | "; 69 | 70 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 71 | 72 | conf.EnvironmentVariables.Should().BeEquivalentTo( 73 | new EnvironmentVariables( 74 | new List 75 | { 76 | new Variable("common_var1", "common_value1", false), 77 | new Variable("common_var2", "common_value2_secured", true) 78 | }, 79 | new List> 80 | { 81 | new ReadOnlyCollection(new List 82 | { 83 | new Variable("db", "mysql", false), 84 | new Variable("password", "mysql_password", false) 85 | }), 86 | new ReadOnlyCollection(new List 87 | { 88 | new Variable("db", "sqlserver", false), 89 | new Variable("password", "sqlserver_secured_password", true) 90 | }) 91 | })); 92 | } 93 | 94 | [Fact] 95 | public void ShouldReadOnlyMatrixVariables() 96 | { 97 | const string yaml = @" 98 | environment: 99 | matrix: 100 | - db: mysql 101 | password: mysql_password 102 | - db: sqlserver 103 | password: 104 | secure: sqlserver_secured_password 105 | "; 106 | 107 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 108 | 109 | conf.EnvironmentVariables.CommonVariables.Should().BeEmpty(); 110 | conf.EnvironmentVariables.Should().BeEquivalentTo( 111 | new EnvironmentVariables( 112 | null, 113 | new List> 114 | { 115 | new ReadOnlyCollection(new List 116 | { 117 | new Variable("db", "mysql", false), 118 | new Variable("password", "mysql_password", false) 119 | }), 120 | new ReadOnlyCollection(new List 121 | { 122 | new Variable("db", "sqlserver", false), 123 | new Variable("password", "sqlserver_secured_password", true) 124 | }) 125 | })); 126 | } 127 | 128 | [Fact] 129 | public void ShouldReadNoEnvironmentButPropertiesShouldNotBeNullOnlyEmpty() 130 | { 131 | var conf = new BuildConfigurationYamlStringReader("").GetBuildConfiguration(); 132 | 133 | conf.EnvironmentVariables.Should().NotBeNull(); 134 | conf.EnvironmentVariables.CommonVariables.Should().BeEmpty(); 135 | conf.EnvironmentVariables.Matrix.Should().BeEmpty(); 136 | } 137 | 138 | [Fact] 139 | public void ShouldExpandVariableValueWhenUsed() 140 | { 141 | Environment.SetEnvironmentVariable("ENV_VAR", "my env value"); 142 | 143 | const string yaml = @" 144 | environment: 145 | common_var1: common_value1 $(ENV_VAR) 146 | "; 147 | 148 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 149 | 150 | conf.EnvironmentVariables.Should().BeEquivalentTo( 151 | new EnvironmentVariables( 152 | new List 153 | { 154 | new Variable("common_var1", "common_value1 $(ENV_VAR)", false) 155 | })); 156 | conf.EnvironmentVariables.CommonVariables[0].Value.Should().Be("common_value1 my env value"); 157 | conf.EnvironmentVariables.Matrix.Should().BeEmpty(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/ExpandableStringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using LocalAppVeyor.Engine.Configuration; 4 | using LocalAppVeyor.Engine.Configuration.Reader; 5 | using Xunit; 6 | 7 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 8 | { 9 | public class ExpandableStringTests 10 | { 11 | [Fact] 12 | public void ExpandEnvironmentVariablesOnString() 13 | { 14 | ExpandableString str = "this a string $(VAR1) and also $(VAR2)"; 15 | 16 | Environment.SetEnvironmentVariable("VAR1", "VAR1_VALUE1"); 17 | Environment.SetEnvironmentVariable("VAR2", "VAR2_VALUE2"); 18 | 19 | str.Should().Be("this a string VAR1_VALUE1 and also VAR2_VALUE2"); 20 | } 21 | 22 | [Fact] 23 | public void DontExpandVariablesWhenOpenButNotClosingBrace() 24 | { 25 | ExpandableString str = "this a string $(VAR1 and also $(VAR2"; 26 | 27 | Environment.SetEnvironmentVariable("VAR1", "VAR1_VALUE1"); 28 | Environment.SetEnvironmentVariable("VAR2", "VAR2_VALUE2"); 29 | 30 | str.Should().Be("this a string $(VAR1 and also $(VAR2"); 31 | } 32 | 33 | [Fact] 34 | public void DontExpandVariablesWithInvalidCharacters() 35 | { 36 | ExpandableString str = "this a string $(VAR 1) and also $(VAR2)"; 37 | 38 | Environment.SetEnvironmentVariable("VAR1", "VAR1_VALUE1"); 39 | Environment.SetEnvironmentVariable("VAR2", "VAR2_VALUE2"); 40 | 41 | str.Should().Be("this a string $(VAR 1) and also VAR2_VALUE2"); 42 | } 43 | 44 | [Fact] 45 | public void Expand() 46 | { 47 | ExpandableString str = "this a string $(VAR1) and also $(VAR2)"; 48 | 49 | Environment.SetEnvironmentVariable("VAR1", "VAR1_VALUE1"); 50 | Environment.SetEnvironmentVariable("VAR2", "VAR2_VALUE2"); 51 | 52 | str.Should().Be("this a string VAR1_VALUE1 and also VAR2_VALUE2"); 53 | } 54 | 55 | [Fact] 56 | public void ShouldExpandCloneFolderWithVersionAndBuildNumber() 57 | { 58 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_BUILD", "0"); 59 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_VERSION", "1.0.0-0"); 60 | 61 | const string yaml = @" 62 | version: 1.0.0-{build} 63 | clone_folder: c:\folder\with\version_{version}_here 64 | "; 65 | 66 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 67 | 68 | conf.Version.Should().Be("1.0.0-0"); 69 | conf.CloneFolder.Should().Be(@"c:\folder\with\version_1.0.0-0_here"); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Configuration/MatrixTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LocalAppVeyor.Engine.Configuration; 3 | using LocalAppVeyor.Engine.Configuration.Reader; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Configuration 7 | { 8 | public class MatrixTests 9 | { 10 | [Fact] 11 | public void MatrixShouldReadAllowFailuresSectionSuccessfully() 12 | { 13 | const string yaml = @" 14 | matrix: 15 | allow_failures: 16 | - platform: x86 17 | configuration: Debug 18 | - platform: x64 19 | configuration: Release 20 | "; 21 | 22 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 23 | 24 | conf.Matrix.Should().BeEquivalentTo(new Matrix( 25 | false, 26 | new[] 27 | { 28 | new AllowedJobFailureConditions(null, "Debug", "x86", null, new Variable[0]), 29 | new AllowedJobFailureConditions(null, "Release", "x64", null, new Variable[0]) 30 | })); 31 | 32 | conf.Matrix.AllowedFailures.Should().HaveCount(2); 33 | } 34 | 35 | [Fact] 36 | public void AllowFailuresSectionShouldBeEmpty() 37 | { 38 | const string yaml = @" 39 | matrix: 40 | "; 41 | 42 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 43 | 44 | conf.Matrix.Should().BeEquivalentTo(new Matrix( 45 | false, 46 | new AllowedJobFailureConditions[0])); 47 | 48 | conf.Matrix.IsFastFinish.Should().BeFalse(); 49 | conf.Matrix.AllowedFailures.Should().BeEmpty(); 50 | } 51 | 52 | [Fact] 53 | public void MatrixShouldHaveDefaultValuesWhenNotSpecified() 54 | { 55 | var conf = new BuildConfigurationYamlStringReader(string.Empty).GetBuildConfiguration(); 56 | 57 | conf.Matrix.Should().BeEquivalentTo(new Matrix()); 58 | conf.Matrix.IsFastFinish.Should().BeFalse(); 59 | conf.Matrix.AllowedFailures.Should().BeEmpty(); 60 | } 61 | 62 | [Fact] 63 | public void MatrixShouldHaveFastFinishAsTrueAndEmptyAllowedFailures() 64 | { 65 | const string yaml = @" 66 | matrix: 67 | fast_finish: true 68 | "; 69 | var conf = new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(); 70 | 71 | conf.Matrix.Should().BeEquivalentTo(new Matrix( 72 | true, 73 | new AllowedJobFailureConditions[0])); 74 | conf.Matrix.IsFastFinish.Should().BeTrue(); 75 | conf.Matrix.AllowedFailures.Should().BeEmpty(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Engine/AssemblyInfoRewriteStepTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Abstractions.TestingHelpers; 4 | using LocalAppVeyor.Engine.Configuration; 5 | using LocalAppVeyor.Engine.Configuration.Reader; 6 | using LocalAppVeyor.Engine.Internal; 7 | using LocalAppVeyor.Engine.Internal.Steps; 8 | using Moq; 9 | using Xunit; 10 | 11 | namespace LocalAppVeyor.Engine.UnitTests.Engine 12 | { 13 | public class AssemblyInfoRewriteStepTests 14 | { 15 | [Fact] 16 | public void AssemblyInfoReWriteShouldReplaceAllVersionAttributes() 17 | { 18 | const string yaml = @" 19 | version: 2.3.{build} 20 | assembly_info: 21 | patch: true 22 | file: AssemblyInfo.* 23 | assembly_version: ""1.1.{build}"" 24 | assembly_file_version: ""{version}"" 25 | assembly_informational_version: ""{version}"" 26 | "; 27 | 28 | const string originalAssemblyInfoContent = @" 29 | [assembly: AssemblyTitle(""PatchAssemblyInfoFiles"")] 30 | [assembly: AssemblyDescription("""")] 31 | [assembly: AssemblyVersion(""1.2.3.4"")] 32 | [assembly: AssemblyFileVersion(""1.2.3.4"")] 33 | [assembly: AssemblyInformationalVersion(""foo bar baz 1 2 3 4"")] 34 | "; 35 | 36 | const string rewrittenAssemblyInfoContent = @" 37 | [assembly: AssemblyTitle(""PatchAssemblyInfoFiles"")] 38 | [assembly: AssemblyDescription("""")] 39 | [assembly: AssemblyVersion(""1.1.0"")] 40 | [assembly: AssemblyFileVersion(""2.3.0"")] 41 | [assembly: AssemblyInformationalVersion(""2.3.0"")] 42 | "; 43 | 44 | var fileSystem = new MockFileSystem(); 45 | var cloneDirectory = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName()); 46 | var repoDirectory = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName()); 47 | var assemblyInfoFilename = fileSystem.Path.Combine(cloneDirectory, "AssemblyInfo.cs"); 48 | fileSystem.AddDirectory(repoDirectory); 49 | fileSystem.AddDirectory(cloneDirectory); 50 | fileSystem.AddFile(assemblyInfoFilename, new MockFileData(originalAssemblyInfoContent)); 51 | fileSystem.Directory.SetCurrentDirectory(repoDirectory); 52 | 53 | var executionContext = new ExecutionContext( 54 | new MatrixJob("os", new List(), "conf", "platform"), 55 | new BuildConfigurationYamlStringReader(yaml).GetBuildConfiguration(), 56 | new Mock().Object, 57 | repoDirectory, 58 | cloneDirectory, 59 | fileSystem); 60 | 61 | Environment.SetEnvironmentVariable("APPVEYOR_BUILD_VERSION", new ExpandableString("2.3.{build}")); 62 | 63 | var rewriteStep = new AssemblyInfoRewriteStep(); 64 | var executionResult = rewriteStep.Execute(executionContext); 65 | 66 | Assert.True(executionResult); 67 | Assert.Equal(rewrittenAssemblyInfoContent, fileSystem.File.ReadAllText(assemblyInfoFilename)); 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Engine/EngineTests.Unix.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | using LocalAppVeyor.Engine.UnitTests.TestUtilities; 3 | using Moq; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Engine 7 | { 8 | public partial class EngineTests 9 | { 10 | [UnixOnlyFact] 11 | public void Linux_ShouldRunInitializationPowershellScript() 12 | { 13 | var buildConfiguration = new BuildConfiguration 14 | { 15 | InitializationScript = 16 | { 17 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test'") 18 | } 19 | }; 20 | 21 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 22 | 23 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test")), Times.Once); 24 | Assert.True(jobResult.IsSuccessfulExecution); 25 | } 26 | 27 | [UnixOnlyFact] 28 | public void Linux_ShouldRunInitializationPowershellScriptWithMultipleLines() 29 | { 30 | var buildConfiguration = new BuildConfiguration 31 | { 32 | InitializationScript = 33 | { 34 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test - first line'"), 35 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test - second line'") 36 | } 37 | }; 38 | 39 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 40 | 41 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - first line")), Times.Once); 42 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - second line")), Times.Once); 43 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 44 | Assert.True(jobResult.IsSuccessfulExecution); 45 | } 46 | 47 | [UnixOnlyFact] 48 | public void Linux_ShouldRunInitializationBashScript() 49 | { 50 | var buildConfiguration = new BuildConfiguration 51 | { 52 | InitializationScript = 53 | { 54 | new ScriptLine(ScriptType.Bash, "echo This is a test") 55 | } 56 | }; 57 | 58 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 59 | 60 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test")), Times.Once); 61 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 62 | Assert.True(jobResult.IsSuccessfulExecution); 63 | } 64 | 65 | [UnixOnlyFact] 66 | public void Linux_ShouldRunInitializationBashScriptWithMultipleLines() 67 | { 68 | var buildConfiguration = new BuildConfiguration 69 | { 70 | InitializationScript = 71 | { 72 | new ScriptLine(ScriptType.Bash, "echo This is a test - first line"), 73 | new ScriptLine(ScriptType.Bash, "echo This is a test - second line") 74 | } 75 | }; 76 | 77 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 78 | 79 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - first line")), Times.Once); 80 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - second line")), Times.Once); 81 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 82 | Assert.True(jobResult.IsSuccessfulExecution); 83 | } 84 | 85 | [UnixOnlyFact] 86 | public void Linux_ShouldRunInitializationScriptWithMixedPowershellAndBashScripts() 87 | { 88 | var buildConfiguration = new BuildConfiguration 89 | { 90 | InitializationScript = 91 | { 92 | new ScriptLine(ScriptType.Bash, "echo This is 1 bash test"), 93 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 1 powershell test'"), 94 | new ScriptLine(ScriptType.Bash, "echo This is 2 bash test"), 95 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 2 powershell test'"), 96 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 3 powershell test'"), 97 | } 98 | }; 99 | 100 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 101 | 102 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 1 bash test")), Times.Once); 103 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 1 powershell test")), Times.Once); 104 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 2 bash test")), Times.Once); 105 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 2 powershell test")), Times.Once); 106 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 3 powershell test")), Times.Once); 107 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 108 | Assert.True(jobResult.IsSuccessfulExecution); 109 | } 110 | 111 | [UnixOnlyFact] 112 | public void Linux_ShouldRunInitializationScriptPowershellBlockScript() 113 | { 114 | var buildConfiguration = new BuildConfiguration 115 | { 116 | InitializationScript = 117 | { 118 | new ScriptLine(ScriptType.PowerShell, @" 119 | Write-Host 'This is line one' 120 | Write-Host 'And this is line two'") 121 | } 122 | }; 123 | 124 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 125 | 126 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is line one")), Times.Once); 127 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "And this is line two")), Times.Once); 128 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 129 | Assert.True(jobResult.IsSuccessfulExecution); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Engine/EngineTests.Windows.cs: -------------------------------------------------------------------------------- 1 | using LocalAppVeyor.Engine.Configuration; 2 | using LocalAppVeyor.Engine.UnitTests.TestUtilities; 3 | using Moq; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Engine 7 | { 8 | public partial class EngineTests 9 | { 10 | [WindowsOnlyFact] 11 | public void Windows_ShouldRunInitializationPowershellScript() 12 | { 13 | var buildConfiguration = new BuildConfiguration 14 | { 15 | InitializationScript = 16 | { 17 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test'") 18 | } 19 | }; 20 | 21 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 22 | 23 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test")), Times.Once); 24 | Assert.True(jobResult.IsSuccessfulExecution); 25 | } 26 | 27 | [WindowsOnlyFact] 28 | public void Windows_ShouldRunInitializationPowershellScriptWithMultipleLines() 29 | { 30 | var buildConfiguration = new BuildConfiguration 31 | { 32 | InitializationScript = 33 | { 34 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test - first line'"), 35 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is a test - second line'") 36 | } 37 | }; 38 | 39 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 40 | 41 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - first line")), Times.Once); 42 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - second line")), Times.Once); 43 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 44 | Assert.True(jobResult.IsSuccessfulExecution); 45 | } 46 | 47 | [WindowsOnlyFact] 48 | public void Windows_ShouldRunInitializationBatchScript() 49 | { 50 | var buildConfiguration = new BuildConfiguration 51 | { 52 | InitializationScript = 53 | { 54 | new ScriptLine(ScriptType.Batch, "echo This is a test") 55 | } 56 | }; 57 | 58 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 59 | 60 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test")), Times.Once); 61 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 62 | Assert.True(jobResult.IsSuccessfulExecution); 63 | } 64 | 65 | [WindowsOnlyFact] 66 | public void Windows_ShouldRunInitializationBatchScriptWithMultipleLines() 67 | { 68 | var buildConfiguration = new BuildConfiguration 69 | { 70 | InitializationScript = 71 | { 72 | new ScriptLine(ScriptType.Batch, "echo This is a test - first line"), 73 | new ScriptLine(ScriptType.Batch, "echo This is a test - second line") 74 | } 75 | }; 76 | 77 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 78 | 79 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - first line")), Times.Once); 80 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is a test - second line")), Times.Once); 81 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 82 | Assert.True(jobResult.IsSuccessfulExecution); 83 | } 84 | 85 | [WindowsOnlyFact] 86 | public void Windows_ShouldRunInitializationScriptWithMixedPowershellAndBatchScripts() 87 | { 88 | var buildConfiguration = new BuildConfiguration 89 | { 90 | InitializationScript = 91 | { 92 | new ScriptLine(ScriptType.Batch, "echo This is 1 batch test"), 93 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 1 powershell test'"), 94 | new ScriptLine(ScriptType.Batch, "echo This is 2 batch test"), 95 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 2 powershell test'"), 96 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is 3 powershell test'"), 97 | } 98 | }; 99 | 100 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 101 | 102 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 1 batch test")), Times.Once); 103 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 1 powershell test")), Times.Once); 104 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 2 batch test")), Times.Once); 105 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 2 powershell test")), Times.Once); 106 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is 3 powershell test")), Times.Once); 107 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 108 | Assert.True(jobResult.IsSuccessfulExecution); 109 | } 110 | 111 | [WindowsOnlyFact] 112 | public void Windows_ShouldRunInitializationScriptPowershellBlockScript() 113 | { 114 | var buildConfiguration = new BuildConfiguration 115 | { 116 | InitializationScript = 117 | { 118 | new ScriptLine(ScriptType.PowerShell, @" 119 | Write-Host 'This is line one' 120 | Write-Host 'And this is line two'") 121 | } 122 | }; 123 | 124 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 125 | 126 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is line one")), Times.Once); 127 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "And this is line two")), Times.Once); 128 | _outputterMock.Verify(outputter => outputter.WriteError(It.IsAny()), Times.Never); 129 | Assert.True(jobResult.IsSuccessfulExecution); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/Engine/EngineTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | using LocalAppVeyor.Engine.Configuration; 3 | using Moq; 4 | using Xunit; 5 | 6 | namespace LocalAppVeyor.Engine.UnitTests.Engine 7 | { 8 | public partial class EngineTests 9 | { 10 | private readonly Mock _outputterMock = new Mock(); 11 | private readonly EngineConfiguration _engineConfiguration; 12 | private readonly IFileSystem _fileSystem = new FileSystem(); 13 | 14 | public EngineTests() 15 | { 16 | var currentDirectory = 17 | _fileSystem.Path.Combine(_fileSystem.Path.GetTempPath(), _fileSystem.Path.GetRandomFileName()); 18 | 19 | if (!_fileSystem.Directory.Exists(currentDirectory)) 20 | { 21 | _fileSystem.Directory.CreateDirectory(currentDirectory); 22 | } 23 | 24 | _fileSystem.Directory.SetCurrentDirectory(currentDirectory); 25 | 26 | _engineConfiguration = new EngineConfiguration(_fileSystem.Directory.GetCurrentDirectory(), _outputterMock.Object, _fileSystem); 27 | } 28 | 29 | [Fact] 30 | public void ShouldSkipSpecifiedBuildStep() 31 | { 32 | var buildConfiguration = new BuildConfiguration 33 | { 34 | InitializationScript = 35 | { 36 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is init step'") 37 | }, 38 | InstallScript = 39 | { 40 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is install step'") 41 | }, 42 | BuildScript = 43 | { 44 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is build step'") 45 | }, 46 | SkipSteps = new [] { "install" } 47 | }; 48 | 49 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 50 | 51 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is init step")), Times.Once); 52 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is install step")), Times.Never); 53 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is build step")), Times.Once); 54 | Assert.True(jobResult.IsSuccessfulExecution); 55 | } 56 | 57 | [Fact] 58 | public void ShouldSkipSpecifiedBuildSteps() 59 | { 60 | var buildConfiguration = new BuildConfiguration 61 | { 62 | InitializationScript = 63 | { 64 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is init step'") 65 | }, 66 | InstallScript = 67 | { 68 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is install step'") 69 | }, 70 | BuildScript = 71 | { 72 | new ScriptLine(ScriptType.PowerShell, "Write-Host 'This is build step'") 73 | }, 74 | SkipSteps = new[] { "install", "build_script" } 75 | }; 76 | 77 | var jobResult = new LocalAppVeyor.Engine.Engine(_engineConfiguration, buildConfiguration).ExecuteJob(0); 78 | 79 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is init step")), Times.Once); 80 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is install step")), Times.Never); 81 | _outputterMock.Verify(outputter => outputter.Write(It.Is(m => m == "This is build step")), Times.Never); 82 | Assert.True(jobResult.IsSuccessfulExecution); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/LocalAppVeyor.Engine.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/TestUtilities/UnixOnlyFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.TestingHelpers; 2 | using Xunit; 3 | 4 | namespace LocalAppVeyor.Engine.UnitTests.TestUtilities 5 | { 6 | internal sealed class UnixOnlyFactAttribute : FactAttribute 7 | { 8 | public UnixOnlyFactAttribute() 9 | { 10 | if (!MockUnixSupport.IsUnixPlatform()) 11 | { 12 | Skip = "Unix-only test"; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/TestUtilities/WindowsOnlyFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.TestingHelpers; 2 | using Xunit; 3 | 4 | namespace LocalAppVeyor.Engine.UnitTests.TestUtilities 5 | { 6 | internal sealed class WindowsOnlyFactAttribute : FactAttribute 7 | { 8 | public WindowsOnlyFactAttribute() 9 | { 10 | if (!MockUnixSupport.IsWindowsPlatform()) 11 | { 12 | Skip = "Windows-only test"; 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/LocalAppVeyor.Engine.UnitTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "parallelizeAssembly": false, 3 | "parallelizeTestCollections": false 4 | } --------------------------------------------------------------------------------