diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml new file mode 100644 index 000000000..4434d09aa --- /dev/null +++ b/.config/1espt/PipelineAutobaseliningConfig.yml @@ -0,0 +1,21 @@ +## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. + +pipelines: + 494: + retail: + binary: + credscan: + lastModifiedDate: 2024-03-13 + binskim: + lastModifiedDate: 2024-03-13 + spotbugs: + lastModifiedDate: 2024-03-13 + source: + credscan: + lastModifiedDate: 2024-03-13 + eslint: + lastModifiedDate: 2024-03-13 + psscriptanalyzer: + lastModifiedDate: 2024-03-13 + armory: + lastModifiedDate: 2024-03-13 diff --git a/.config/credscan/Suppressions.json b/.config/credscan/Suppressions.json new file mode 100644 index 000000000..419b41b02 --- /dev/null +++ b/.config/credscan/Suppressions.json @@ -0,0 +1,9 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "artifacts/bin/Microsoft.TryDotNet.IntegrationTests/Release/net9.0/.playwright/package/ThirdPartyNotices.txt", + "_justification": "This file is a third party notice text file, but not a real issue, and not one we distribute" + } + ] +} \ No newline at end of file diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines new file mode 100644 index 000000000..e0a1e0789 --- /dev/null +++ b/.config/guardian/.gdnbaselines @@ -0,0 +1,29 @@ +{ + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines" + }, + "version": "1.0.0", + "baselines": { + "default": { + "name": "default", + "createdDate": "2024-03-13 17:27:58Z", + "lastUpdatedDate": "2024-03-13 17:27:58Z" + } + }, + "results": { + "dadd4935cc62146c68a2bc507ab14dbdaa10b05aed99d1c0afcf677bd22a0dba": { + "signature": "dadd4935cc62146c68a2bc507ab14dbdaa10b05aed99d1c0afcf677bd22a0dba", + "alternativeSignatures": [], + "target": "artifacts/bin/Microsoft.TryDotNet.IntegrationTests/Release/net8.0/.playwright/package/ThirdPartyNotices.txt", + "line": 1240, + "memberOf": [ + "default" + ], + "tool": "credscan", + "ruleId": "CSCAN-GENERAL0030", + "createdDate": "2024-03-13 17:44:29Z", + "expirationDate": "2024-08-30 18:33:33Z", + "justification": "This error is baselined with an expiration date of 180 days from 2024-03-13 18:33:33Z" + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..acdab74f7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,31 @@ + +[*.{c,c++,cc,cp,cpp,cu,cuh,cxx,h,hh,hpp,hxx,inc,inl,ino,ipp,mpp,proto,tpp}] +indent_style=tab +indent_size=tab +tab_width=4 + +[*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] +indent_style=space +indent_size=4 +tab_width=4 + +[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] +indent_style=space +indent_size=2 +tab_width=2 + +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers=false +csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_style_var_elsewhere=true:hint +csharp_style_var_for_built_in_types=true:hint +csharp_style_var_when_type_is_apparent=true:hint +dotnet_style_predefined_type_for_locals_parameters_members=true:hint +dotnet_style_predefined_type_for_member_access=true:hint +dotnet_style_qualification_for_event=false:warning +dotnet_style_qualification_for_field=false:warning +dotnet_style_qualification_for_method=false:warning +dotnet_style_qualification_for_property=false:warning +dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9dacbadfe..bda5d84d6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,33 +1,34 @@ --- name: Bug report about: Create a report to help us improve +title: '' +labels: '' +assignees: '' --- -**Describe the bug** +#### Describe the bug Please provide as much information as you can. -**Where did this error occur?** -- docs.microsoft.com -- microsoft.com/net/learn/dotnet/in-browser-tutorial -- [Noda Time](https://nodatime.org/2.3.x/userguide/recipes) -- try.dot.net - *Please note: this is our production site* +#### What is the URL where you saw the error? -**What kind of error was it?** -- User Interface (UI): For example the output never displayed -- Service Error: For example "The service is temporarily unavailable. We are working on it" -- Other : +#### What kind of error was it? +- [ ] User Interface (UI): For example the output never displayed +- [ ] Service Error: For example "The service is temporarily unavailable. We are working on it" +- [ ] Other: -**Screenshots** +#### Screenshots If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] +#### Please complete the following: + - OS + - [ ] Windows + - [ ] macOS + - [ ] Linux (Please specify distro) + - [ ] iOS + - [ ] Android + - Browser + - [ ] Chrome + - [ ] Edge + - [ ] Firefox + - [ ] Safari diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 634ac4aa3..21a0a2b29 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,9 @@ --- name: Feature request about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' --- @@ -14,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Read More** -https://github.com/dotnet/try/wiki/Product-Roadmap +https://github.com/dotnet/try/wiki/Project-Roadmap diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..beb49b1ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,404 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +.trydotnet-* +.dotnet/ + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +.packages + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* +*.ncrunch*.user + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +wwwroot/api/ + +wwwroot/client/ + +MLS\.Agent/wwwroot/api/ + +MLS\.Agent/wwwroot/client/ + +MLS\.Agent/wwwroot/css/ + +*.trx + + +Microsoft\.DotNet\.Try\.js/\.rpt2_cache/ + +Microsoft\.DotNet\.Try\.js/dist/ + +docs/\.ionide/ + +\.ionide/ + + +.ipynb_checkpoints/ + +#Backup files +*.bak +NotebookExamples/*/Samples/housing.csv + +src/microsoft-trydotnet-editor/test-results/ + +src/microsoft-trydotnet-editor/dist/ + +src/microsoft-trydotnet-editor/simulatorGenerator/ + +src/microsoft-trydotnet-editor/lib/ + +src/microsoft-trydotnet/test-results/ + +src/microsoft-trydotnet/dist/ + +src/microsoft-trydotnet/simulatorGenerator/ + +src/microsoft-trydotnet/lib/ + +src/Microsoft.TryDotNet/wwwroot/api/ + +src/Microsoft.TryDotNet/wwwroot/css/trydotnet.css + +src/microsoft-learn-mock/dist/ + +src/microsoft-learn-mock/site/index.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..1c25627c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/MLS.Agent/bin/Debug/netcoreapp2.1/MLS.Agent.dll", + "args": [], + "cwd": "${workspaceFolder}/MLS.Agent", + "stopAtEntry": false, + "launchBrowser": { + "enabled": true + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..7f104673c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,36 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/MLS.Agent/MLS.Agent.csproj" + ], + "problemMatcher": "$tsc" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/MLS.Agent/MLS.Agent.csproj" + ], + "problemMatcher": "$tsc" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/MLS.Agent/MLS.Agent.csproj" + ], + "problemMatcher": "$tsc" + } + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0fd65e978 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant +to clarify expected behavior in our community. +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/Developer-guide.ipynb b/Developer-guide.ipynb new file mode 100644 index 000000000..41ff3c797 --- /dev/null +++ b/Developer-guide.ipynb @@ -0,0 +1,503 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prerequisites\n", + "\n", + "In order to build Try .NET, you'll need the following installed:\n", + "\n", + "* The [.NET 9 SDK](https://learn.microsoft.com/en-us/dotnet/core/install/).\n", + "* The LTS version of [Node.js](https://nodejs.org/en/download)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1: [Optional] Build .NET Interactive\n", + "\n", + "The core functionality in Try .NET is the .NET Interactive repo, which contains the `CSharpProjectKernel` that powers Try .NET's compilation and language services. \n", + "\n", + "* If you're making changes in that layer, then you'll need to build the .NET Interactive packages locally. \n", + "\n", + "* If not, skip to step 2.1." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.1: Clone, build, and pack .NET Interactive\n", + "\n", + "First, make sure you have the [.NET Interactive repo](https://github.com/dotnet/interactive) forked and cloned. Set the path to the repo root in the following cell. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$dotnetInteractiveRepoPath = Read-Host -Prompt \"Enter the path to the root of your dotnet/interactive clone\"\n", + "$dotnetInteractiveRepoPath" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Follow the instructions to build .NET Interactive in the [developer guide](https://github.com/dotnet/interactive/blob/main/DEVELOPER-GUIDE.md). \n", + "\n", + "You don't need to set up a local build of the Polyglot Notebooks extension to work on Try .NET.\n", + "\n", + "The following script will build the project and copy the generated packages to C:\\temp\\packages\\ which will be be used a NuGet package source when building Try .NET." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Invoke-Expression \"$dotnetInteractiveRepoPath\\repack.ps1\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.2: Update Microsoft.DotNet.Interactive.CSharpProject version used by Try .NET\n", + "\n", + "This step only needs to be done if you're changing code from the .NET Interactive repo.\n", + "\n", + "This step modifies NuGet.config to reference the local package source where .NET Interactive packages build in Step 1.* should be found." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$tryDotnetRepoPath = Get-Location\n", + "\n", + "$nugetConfigPath = Join-Path $tryDotnetRepoPath 'NuGet.config'\n", + "[xml]$nugetConfig = Get-Content $nugetConfigPath\n", + "\n", + "$newPackageSource = $nugetConfig.CreateElement(\"add\")\n", + "$newPackageSource.SetAttribute(\"key\", \"LocalDevPackages\")\n", + "$newPackageSource.SetAttribute(\"value\", \"C:\\temp\\packages\")\n", + "\n", + "$packageSourcesNode = $nugetConfig.SelectSingleNode('configuration/packageSources')\n", + "$packageSourcesNode.AppendChild($newPackageSource) > $null\n", + "\n", + "$packageMappingNode = $nugetConfig.CreateElement(\"packageSource\")\n", + "$packageMappingNode.SetAttribute(\"key\", \"LocalDevPackages\")\n", + "$packageNode = $nugetConfig.CreateElement(\"package\")\n", + "$packageNode.SetAttribute(\"pattern\", \"*\")\n", + "$packageMappingNode.AppendChild($packageNode) > $null\n", + "\n", + "$packageSourcesNode = $nugetConfig.SelectSingleNode('configuration/packageSourceMapping')\n", + "$packageSourcesNode.AppendChild($packageMappingNode) > $null\n", + "\n", + "$nugetConfig.Save($nugetConfigPath)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, update the referenced version of the package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "# Define the path to the Directory.Packages.props file\n", + "$packagesPropsPath = \"$tryDotnetRepoPath\\Directory.Packages.props\"\n", + "\n", + "# Load the XML content of the Directory.Packages.props file\n", + "[xml]$packagesProps = Get-Content $packagesPropsPath\n", + "\n", + "# Define the namespace to handle the default namespace in the document\n", + "$namespaceManager = New-Object System.Xml.XmlNamespaceManager($packagesProps.NameTable)\n", + "$namespaceManager.AddNamespace(\"msb\", \"http://schemas.microsoft.com/developer/msbuild/2003\")\n", + "\n", + "# Ensure that the ItemGroup element exists\n", + "$itemGroup = $packagesProps.SelectSingleNode(\"msb:Project/msb:ItemGroup\", $namespaceManager)\n", + "if ($itemGroup -eq $null) {\n", + " $itemGroup = $packagesProps.CreateElement(\"ItemGroup\", $namespaceManager.LookupNamespace(\"msb\"))\n", + " $packagesProps.DocumentElement.AppendChild($itemGroup)\n", + "}\n", + "\n", + "# Check if the PackageVersion element for the package already exists\n", + "$packageRef = $itemGroup.SelectSingleNode(\"msb:PackageVersion[@Include='Microsoft.DotNet.Interactive.CSharpProject']\", $namespaceManager)\n", + "\n", + "if ($packageRef -eq $null) {\n", + " # If the package reference doesn't exist, create it\n", + " $newPackageRef = $packagesProps.CreateElement(\"PackageVersion\", $namespaceManager.LookupNamespace(\"msb\"))\n", + " $newPackageRef.SetAttribute(\"Include\", \"Microsoft.DotNet.Interactive.CSharpProject\")\n", + " $newPackageRef.SetAttribute(\"Version\", \"2.0.0\")\n", + " $itemGroup.AppendChild($newPackageRef)\n", + "} else {\n", + " # If it exists, just update the version\n", + " $packageRef.SetAttribute(\"Version\", \"2.0.0\")\n", + "}\n", + "\n", + "# Save the changes back to the Directory.Packages.props file\n", + "$packagesProps.Save($packagesPropsPath)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.1: Build Try .NET" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "$tryDotnetRepoPath = Get-Location\n", + "\n", + "$tryDotNetProjectFilePath=\"$tryDotnetRepoPath\\src\\Microsoft.TryDotNet\\Microsoft.TryDotNet.csproj\"\n", + "$mockMsLearnServerPath=\"$tryDotnetRepoPath\\src\\microsoft-learn-mock\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, build the client pieces, which includes the `trydotnet.js` library." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "if ($IsWindows) {\n", + " Invoke-Expression \"$tryDotnetRepoPath\\build-js.cmd\"\n", + "} else {\n", + " Invoke-Expression \"$tryDotnetRepoPath/build-js.sh\"\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, build the Try .NET service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "dotnet build -c Debug $tryDotnetRepoPath" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.2: Start the Try .NET service\n", + "\n", + "This launches the Try .NET web service. It will launch in a separate terminal window. Closing that window will stop the server." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Start-Process pwsh -ArgumentList \"-c dotnet run --no-build --project ${tryDotNetProjectFilePath} --launch-profile TryDotNet-Development\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* _Note: If the above crashes with an error that looks like the following, the issue is hardcoded paths in the `Prebuild` folder. If you delete them, they will be regenerated on startup._\n", + "\n", + " ```console\n", + " Logging to: c:\\temp\n", + " Unhandled exception. System.IO.FileNotFoundException: Could not find a part of the path 'C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\8.0.8\\ref\\net8.0\\Microsoft.CSharp.dll'.\n", + " File name: 'C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\8.0.8\\ref\\net8.0\\Microsoft.CSharp.dll'\n", + " ---> System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\8.0.8\\ref\\net8.0\\Microsoft.CSharp.dll'.\n", + " ```\n", + "\n", + " _The following cell will tell you where the prebuilds are located on your machine. You can delete this directory entirely to allow the prebuilds to be rebuilt when the Try .NET service starts._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, + "outputs": [], + "source": [ + "using System.IO;\n", + "\n", + "string PrebuildPathEnvironmentVariableName = \"TRYDOTNET_PREBUILDS_PATH\";\n", + "\n", + "var environmentVariable = Environment.GetEnvironmentVariable(PrebuildPathEnvironmentVariableName);\n", + "\n", + "var prebuildsLocation =\n", + " environmentVariable is not null\n", + " ? new DirectoryInfo(environmentVariable)\n", + " : new DirectoryInfo(\n", + " Path.Combine(\n", + " Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n", + " \".trydotnet\",\n", + " \"prebuilds\"));\n", + "\n", + "prebuildsLocation.ToString().Display();\n", + "\n", + "// UNCOMMENT TO DELETE THIS FOLDER AND RESET\n", + "// prebuildsLocation.Delete(recursive: true);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.3: Verify Try .NET by running Peaky tests" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When the server is up and running, you should see something like this:\n", + "\n", + "![image.png](attachment:image.png)\n", + "\n", + "You can now verify basic (server-side) functionality by running the embedded [Peaky](https://github.com/PeakyTests/Peaky) tests." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, + "outputs": [], + "source": [ + "#r \"nuget:Peaky.Client\"\n", + "\n", + "using Peaky.Client;\n", + "using System.Net.Http;\n", + "\n", + "var peakyClient = new PeakyClient(new Uri(\"https://localhost:7061/tests\"));\n", + "\n", + "var tests = (await peakyClient.GetTestsAsync()).Where(t => t.Tags.Any(tag => tag is \"self\"));\n", + "\n", + "foreach (var test in tests)\n", + "{\n", + " var result = await test.GetResultAsync();\n", + " result.Display();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.4: Build the MS Learn mock server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Set-Location -Path $mockMsLearnServerPath\n", + "npm ci\n", + "npm run buildProd\n", + "Set-Location $tryDotnetRepoPath" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.5: Launch the MS Learn mock server\n", + "\n", + "This site hosts the Try .NET IFRAME and provides controls so you can see if things are working. It will launch in a separate terminal window. Closing that window will stop the server." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Set-Location -Path $mockMsLearnServerPath\n", + "\n", + "Start-Process pwsh -ArgumentList \"-c npx http-server -p 7063\"\n", + "\n", + "Set-Location $tryDotnetRepoPath" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.6: Open the MS Learn mock site and interact with Try .NET" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "pwsh" + }, + "polyglot_notebook": { + "kernelName": "pwsh" + } + }, + "outputs": [], + "source": [ + "Start-Process \"http://127.0.0.1:7063/site/DocsHost.html?trydotnetUrl=https://localhost:7061/api/trydotnet.min.js&trydotnetOrigin=https://localhost:7061/\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Try .NET / Microsoft Learn flow diagram\n", + "\n", + "The diagram available through the link below provides a comprehensive view of the interaction between the Try .NET service and a host page (e.g., learn.microsoft.com).\n", + "\n", + "https://github.com/dotnet/interactive/tree/main/src/Microsoft.DotNet.Interactive.CSharpProject#try-net--microsoft-learn-flow\n", + "\n", + "The core of Try .NET is powered by the `CSharpProjectKernel`, which does most of the work powering the Try .NET service." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "name": "polyglot-notebook" + }, + "polyglot_notebook": { + "kernelInfo": { + "defaultKernelName": "csharp", + "items": [ + { + "aliases": [], + "languageName": "csharp", + "name": "csharp" + } + ] + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..c4c081f23 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,30 @@ + + + + + + + + + true + + false + false + false + + + $(NoWarn);NU5104 + + + + + 13.0.3 + 0.3.0 + 4.3.0 + 4.3.2 + 4.3.0 + 4.5.0 + 4.5.0 + 7.0.0 + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..f0739ac4e --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..a795acae9 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,37 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..88adbf475 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0-azurelinux3.0 AS build-env +WORKDIR /App + +# Copy everything +COPY . ./ + +# Make sure we run bash +CMD ["bash"] + +# Make sure we get all the updates and tools we need to build +RUN tdnf install gawk -y +# This is Node v16. For 18, use nodejs18. +RUN tdnf install nodejs -y +RUN tdnf install npm -y +RUN tdnf clean all + +# Build javascript library +RUN /App/build-js.sh + +# Restore +RUN dotnet restore --configfile /App/NuGet.config /App/TryDotNet.sln + +# Build and publish a release +RUN dotnet publish -c Release -o out /App/src/Microsoft.TryDotNet + +# Build runtime image +FROM mcr.microsoft.com/dotnet/sdk:9.0-azurelinux3.0 +ARG TRY_DOT_NET_BUILD_ID +WORKDIR /App + +# Make sure we run bash +CMD ["bash"] + +# Make sure we get all the tools we need +RUN tdnf install procps -y +RUN tdnf clean all + +# Copy from build image +COPY --from=build-env /App/out . + +# Set up to run and expose app on port 80 +EXPOSE 80 +ENV ASPNETCORE_URLS=http://*:80/ + +# This is a workaround for the fact that the Try .NET website is not yet container-aware +ENV TRY_DOT_NET_REQUEST_SCHEME=https +ENV TRY_DOT_NET_BUILD_ID=$TRY_DOT_NET_BUILD_ID +ENV TRY_DOT_NET_MANUAL_BUILD_ID=2 + +# Run the Microsoft.TryDotNet website +ENTRYPOINT ["dotnet", "Microsoft.TryDotNet.dll"] diff --git a/DotNetTry.md b/DotNetTry.md new file mode 100644 index 000000000..1803ef712 --- /dev/null +++ b/DotNetTry.md @@ -0,0 +1,28 @@ +# Try .NET dotnet bot in space +|| [**Basics**](#basics) • [**Experiences**](#experiences) || [**Setup**](#setup) • [**Getting Started**](#getting-started) || [**Samples**](https://github.com/dotnet/try-samples) || + +![Try_.NET Enabled](https://img.shields.io/badge/Try_.NET-Enabled-501078.svg) + +[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/try/try-ci?branchName=main)](https://dev.azure.com/dnceng-public/public/_build?definitionId=62&branchName=main) + +**What is Try .NET?**: Try .NET is an interactive documentation generator for .NET Core. + +## Experiences + Use Try .NET to create executable C# snippets for your websites, or interactive markdown files that users can run on their machine. + +### Online, powered by Blazor + +Microsoft Learn uses Try .NET to create interactive documentation. Users can run and edit code all in the browser. + +_**Please Note**: At the moment, the Try .NET online experience only works with C# documentation._ + + + +### Interactive .NET documentation + +Try .NET enables .NET developers to create interactive markdown files. +To make your markdown files interactive, you will need to use [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0), the dotnet try global tool and [Visual Studio](https://visualstudio.microsoft.com/) / [VS Code](https://code.visualstudio.com/) (or any other editor of your choice). + + + +To get started follow [setup](DotNetTryLocal.md) instructions diff --git a/DotNetTryLocal.md b/DotNetTryLocal.md new file mode 100644 index 000000000..f5646936a --- /dev/null +++ b/DotNetTryLocal.md @@ -0,0 +1,76 @@ +# Getting started with dotnet try +## Setup +Before you get can started creating interactive documentation, you will need to install the following: + +- The [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0) +- The [.NET Core 2.1 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.1) +- The [dotnet try](https://www.nuget.org/packages/Microsoft.dotnet-try/) global tool + +Updating to the latest version of the tool is easy. Run the following command: + +```console +> dotnet tool update -g Microsoft.dotnet-try +``` + +## Installing preview builds from `master` + +To install the latest preview build from master, first uninstall the existing version of the tool: + +```console +> dotnet tool uninstall -g Microsoft.dotnet-try +``` +Then, install from the preview package feed: + +```console +> dotnet tool install -g --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" Microsoft.dotnet-try +``` + +## Getting Started + +You can get started using either one of the options below. + +### Option1: `dotnet try demo` + +- Create a new folder. +- `cd` to your new folder. +- Run command `dotnet try demo` : This will load our interactive dotnet try getting started tutorial. + +The tutorials below work you through the following: + +- Creating a new Try .NET project. +- Display interactive snippets using C# regions. +- Creating Sessions +- Verifying your projects: `dotnet try verify` a compiler for your documentation. +- Passing Arguments +- Using read only snippets + +### Option 2: Starting from scratch. +1. Go to the terminal and create a folder called `mydoc`. +2. `cd` to the `mydoc` folder and create a new console app with the following command: + +```console +> dotnet new console -o myApp +``` +This will create a console app with the files `myApp.csproj` and `Program.cs`. + +3. Open the `mydoc` folder in Visual Studio Code. + +4. Create a file called `doc.md`. Inside that file, add some text and a code fence: + +````markdown +# My Interactive Document: + +```cs --source-file ./myApp/Program.cs --project ./myApp/myApp.csproj +``` +```` +5. Now, navigate to the `mydoc` folder in your console and run the following command: +```console + > dotnet try +``` +You have created your first C# interactive developer experience. You should now be able to run your console app and see the result in the browser. + +### Option 3: Explore our [samples repo](https://github.com/dotnet/try-samples). +- Clone the [dotnet/try-samples](https://github.com/dotnet/try-samples) repo. +- Follow the quick steps listed [here](https://github.com/dotnet/try-samples#basics) to get started. + +Return to [README.md](README.md) diff --git a/LICENSE b/License.txt similarity index 92% rename from LICENSE rename to License.txt index 5333fd5b0..c35b1079d 100644 --- a/LICENSE +++ b/License.txt @@ -1,6 +1,7 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2017 .NET Foundation +Copyright (c) Microsoft Corporation. +All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +20,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..689748dac --- /dev/null +++ b/NuGet.config @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PeakyTests.ipynb b/PeakyTests.ipynb new file mode 100644 index 000000000..55fa4b832 --- /dev/null +++ b/PeakyTests.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "#r \"nuget:Peaky.Client\"\n", + "\n", + "using Peaky.Client;\n", + "using System.Net.Http;\n", + "\n", + "// var peakyClient = new PeakyClient(new Uri(\"https://localhost:7061/tests\"));\n", + "var peakyClient = new PeakyClient(new Uri(\"https://try-ppe.dot.net/tests\"));" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "var tests = (await peakyClient.GetTestsAsync( )) \n", + " .Where(t => t.Tags.Any(tag => tag == \"self\"))\n", + " ;\n", + "tests.Display();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "foreach (var test in tests)\n", + "{\n", + " var result = await test.GetResultAsync();\n", + " result.Display();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/README.md b/README.md index 1c5665b79..7c3860353 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,56 @@ +# After nearly 8 years and over 1 billion code executions, Try .NET is **retiring on December 31st, 2025**. + +When we launched in 2017, GitHub Codespaces didn't exist yet, C# Dev Kit was years away, and AI-powered coding wasn't on the horizon. Try .NET gave developers something they needed: the ability to experiment with C# directly in their browser, no setup required. + +We're incredibly proud of what Try .NET accomplished and grateful to everyone who used it, learned with it, and contributed to it. You helped shape what came next and the world has evolved. Today's developers have powerful local development tools that would have seemed like magic back then. It's time for us to focus our energy there. + +Thank you for being part of the journey. Try .NET may be retiring, but the mission to make C# development delightful and accessible continues. +**Get started with modern C#:** [C# Dev Kit tutorial](https://dotnet.microsoft.com/en-us/learn/dotnet/hello-world-tutorial) + +--- + # Try .NET dotnet bot in space -|| [**Basics**](#basics) • [**Experiences**](#experiences) || [**Setup**](#setup) • [**Getting Started**](#getting-started) || [**Samples**](Samples) || -![Try_.NET Enabled](https://img.shields.io/badge/Try_.NET-Enabled-501078.svg) +## Welcome to the Try .NET repo. -## Basics -**What is Try .NET**: Try .NET is an interactive documentation generator for .NET Core. +Try .NET provides developers and content authors with tools to create interactive experiences. -**Please Note**: At the moment Try .NET only works with C# documentation. +There are two different Try .NET interactive experiences: +* The web experience powered by Blazor (as seen on [Microsoft Learn](https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/tutorials/hello-world)) +* Interactive documentation with Markdown for .NET Core with the [dotnet try global tool](DotNetTry.md). -**What is the repo for?** -- A place where you can post issues about [Try .NET](https://github.com/dotnet/try/issues). -- **Note:** The code for Try .NET is not currently hosted on GitHub.*(Coming soon)* +### Online, powered by Blazor -## Experiences - Use Try .NET to create executable C# snippets for your websites or, interactive markdown files that users can run on their machine. +Microsoft Learn uses Try .NET to create interactive documentation. Users can run and edit code all in the browser. -**Websites** +_**Please Note**: At the moment, the Try .NET online (`trydotnet.js API`) is currently only used internally at Microsoft as seen on Learn and [.NET page](https://dotnet.microsoft.com/learn/dotnet/in-browser-tutorial/1)._ -Microsoft Docs uses Try .NET to create interactive documentation. Users can run and edit code in the browser. -![Try NET_online](https://user-images.githubusercontent.com/2546640/57144765-c850cc00-6d8f-11e9-982d-50d2b6dc3591.gif) + -**Interactive .NET documentation** +### Interactive .NET Core documentation with the `dotnet try` global tool Try .NET enables .NET developers to create interactive markdown files. -To make your markdown files interactive, you will need to [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0), the dotnet try global tool(*coming soon*) and [Visual Studio](https://visualstudio.microsoft.com/) / [VS Code](https://code.visualstudio.com/)(or any other editor of your choice). -![interactive_doc](https://user-images.githubusercontent.com/2546640/57158389-47a2c780-6db1-11e9-96ad-8c6e9ab52853.png) - -## Setup -Before you get can start creating interactive documentation, you will need to install the following: -- [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0) -- [dotnet try global tool](*coming soon*) -Once you have successfully installed `dotnet try` global tool, enter the command `dotnet try -h` you will see a list of commands: - -| Command | Purpose | -|----------------|----------------------------------------| -| `demo` | Learn how to create Try .NET content with an interactive demo | -| `verify` | Verify Markdown files in the target directory and its children. | -## Getting Started - -You can get started using either one of the options below. - -**Option1**: `dotnet try demo` -- Create a new folder. -- `cd` to your new folder. -- Run command `dotnet try demo` - -**Option 2**: Starting from scratch. -1. Go to the terminal and create a folder called `mydoc`. -2. `cd` to the `mydoc` folder and create a new console app with the following command - ```console - > dotnet new console -o myApp -``` -This will create a console app with the files `myApp.csproj` and `Program.cs`. - -3. Open `mydoc`folder in Visual Studio Code. - -4. Create a file called `doc.md`. Inside that file, add some text and a code fence: - -````markdown -# My Interactive Document: - -```cs --source-file ./myApp/Program.cs --project ./myApp/myApp.csproj -``` -```` -5. Now, navigate back to the `mydoc` folder and run the following command: -```console - > dotnet try -``` -You have created your first C# interactive developer experience. You should now be able to run your console app and see the result in the browser. - -**Option 3**: Explore our [samples folders](Samples). -- Read our quick [setup guide](Samples/Setup.md). +To make your markdown files interactive, you will need the [.NET Core 3.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.0), the `dotnet try` global tool and [Visual Studio](https://visualstudio.microsoft.com/) / [VS Code](https://code.visualstudio.com/) (or any other editor of your choice). + + + +## Table of Contents +- [Getting Started with Try .NET](DotNetTryLocal.md): Interactive documentation generator for .NET Core. + + +## Contribution Guidelines + +As we are still in the early stages of our development, we are unable to take any feature PRs at the moment, but we intend to do so in the future. +If you find an issue or have a feature suggestion, please open an [issue](https://github.com/dotnet/try/issues/new/choose). And if you have any feature suggestions, please submit them using the "community suggestions" label. + +## Customers & Partners + +**Microsoft Learn** + +Microsoft Learn uses [Try .NET](https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/tutorials/hello-world) to create interactive documentation. Users can run and edit .NET code in the browser. + +## Community Showcase + +**NCache Live by Alachisoft** + +[NCache Live](https://www.alachisoft.com/blogs/try-ncache-anywhere-anytime-with-ncache-live/) uses [Try .NET](https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/tutorials/hello-world) to let the user experience NCache APIs with the help of executable and editable .NET code in the browser. diff --git a/Samples/Beginners/HelloWorld.md b/Samples/Beginners/HelloWorld.md deleted file mode 100644 index 94f6674fb..000000000 --- a/Samples/Beginners/HelloWorld.md +++ /dev/null @@ -1,12 +0,0 @@ -# Intro to Programming - -### Run your First Program -Just click the `run` icon to get started - -``` cs --region intro --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj -Console.WriteLine("Hello World!"); -``` -Congratulations! You've run your first C# program. It used the Console.WriteLine method to print `Hello World`. `Console` is a type that represents the console window. `WriteLine` is a method of the `Console` type that prints a line of text. - - -#### Next: [Strings & Variables »](./Strings.md) Previous: [Home «](../README.md) \ No newline at end of file diff --git a/Samples/Beginners/Methods.md b/Samples/Beginners/Methods.md deleted file mode 100644 index 7155abc83..000000000 --- a/Samples/Beginners/Methods.md +++ /dev/null @@ -1,21 +0,0 @@ -# Learning Methods and Collections - -### Methods -A **method** is a block of code that implements some action. `ToUpper()` is a method you can invoke on a string, like the *name* variable. It will return the same string, converted to uppercase. -``` cs --region methods --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj -var name ="Rain"; -Console.WriteLine($"Hello {name.ToUpper()}!"); -``` -### Collections -**Collections** hold multiple values of the same type. - -Replace the *name* variable with a *names* variable that has a list of names. Then use a `foreach loop` to iterate over all the names and say hello to each person. - -``` cs --region collections --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj - var names = new List { "Rain", "Sage", "Lee" }; - foreach (var name in names) - { - Console.WriteLine($"Hello {name.ToUpper()}!"); - } -``` -#### Previous - [Strings & Variables «](./Strings.md) Home - [Home](../README.md) \ No newline at end of file diff --git a/Samples/Beginners/Strings.md b/Samples/Beginners/Strings.md deleted file mode 100644 index 155143d2e..000000000 --- a/Samples/Beginners/Strings.md +++ /dev/null @@ -1,27 +0,0 @@ -# Learning Strings - -### Strings -Try modifying the code so that the console says hello to *your name*, instead of the *world*. For example, I used the *Rain*. Replace *Rain* with your name. - -``` cs --region strings --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj -Console.WriteLine("Hello Rain"); -``` -### Variables -In programming we want to store values in a container so we can use them later; these are called `variables`. -Let's store your name in a variable, then read the value from that variable when creating the output message. - -``` cs --region variables --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj -var name = "Rain"; -Console.WriteLine("Hello " + name + "!"); -``` -The first line `var name = "Your Name";` declares a variable, *name* and assigns it a value,`Your Name`. The second line prints out the name. - -### String Interpolation -You've been using `+` to combine variables and constant strings. There is a cleaner way to do this. -Start with including a `$` before the opening quotes of the string. Now, place a variable between curly braces `{ variable name}`, and this will tell C# to replace `variable name` with the value of the variable. This is called **string interpolation**. - -``` cs --region interpolation --source-file .\myapp\Program.cs --project .\myapp\myapp.csproj -var name = "Rain"; -Console.WriteLine($"Hello {name}!"); -``` -#### Next: [Methods »](./Methods.md) Previous: [Hello World «](./HelloWorld.md) Home - [Home](../README.md) diff --git a/Samples/Beginners/TeachTheComputer-formatting.md b/Samples/Beginners/TeachTheComputer-formatting.md deleted file mode 100644 index 4d2f6fc77..000000000 --- a/Samples/Beginners/TeachTheComputer-formatting.md +++ /dev/null @@ -1,32 +0,0 @@ -# Let the computer repeat tasks - -Your code should have looked like the following once you finished updating the initialization: - -``` cs --region initialize-in-loop --source-file .\myapp\PascalsTriangle.cs --project .\myapp\myapp.csproj -``` - -That's better. The last step is to improve the formatting. Replace the existing loop to print the rows of the triangle with the following code: - -```csharp -const int fieldWidth = 6; -foreach (int[] row in triangle) -{ - int indent = (5- row.Length) * (fieldWidth / 2); - Console.Write(new string(' ', indent)); - foreach (var item in row) - Console.Write($"{item, fieldWidth}"); - Console.WriteLine(); -} -``` - -This final version makes it easy to build more and more rows. You may notice the number `5` in many places in this code. You can declare a constant instead.: - -```csharp -const int MaxRows = 5; -``` - -Then, replace all instances of `5` with `MaxRows`. Now, if you change `MaxRows`, you'll generate however many rows you want!. Well, within reason. In this environment, your program will time out if go too far. - -You've finished this tutorial. You've taught the computer to do some of your math homework. - -#### Previous: [Build the triangle «](./TeachTheComputer-repetition-2.md) Home: [Home](../README.md) diff --git a/Samples/Beginners/TeachTheComputer-repetition-2.md b/Samples/Beginners/TeachTheComputer-repetition-2.md deleted file mode 100644 index eaac06499..000000000 --- a/Samples/Beginners/TeachTheComputer-repetition-2.md +++ /dev/null @@ -1,41 +0,0 @@ -# Let the computer repeat tasks - -Your code should have looked like the following once you finished creating the array of rows: - -``` cs --region more-arrays --source-file .\myapp\PascalsTriangle.cs --project .\myapp\myapp.csproj -``` - -That's better. Now, let's update this code to make the computers do the repetitive work instead of you. When you copied and pasted the code for each row, you probably did something like the following: - -1. Paste the previous row. -1. Add 1 to the index of the row. -1. Copy and paste one element in the array, then increase the column indexes. - -Let's make the computer do that for us. Start by removing the statements that initialize the rows of the array. Replace those statements with loop to run for rows 0 through 4: - -```csharp -for (int rowIndex = 0; rowIndex < 5; rowIndex++) -{ -} -``` - -Add code to create a new array for the row. Then set the `1` values for the first and last values: - -```csharp -triangle[rowIndex] = new int[rowIndex + 1]; -triangle[rowIndex][0] = 1; - -triangle[rowIndex][rowIndex] = 1; -``` - -Next, set the columns between the first and last using the previous row. Put the following code where the blank line is in the previous example: - -```csharp -for (int column = 1; column < rowIndex; column++) - triangle[rowIndex][column] = triangle[rowIndex - 1][column - 1] + triangle[rowIndex - 1][column]; -``` - - -Try to modify the sample at the top of the page using these instructions. Then continue to see the answer and improve the formatting. - -#### Next: [Improve formatting »](./TeachTheComputer-formatting.md) Previous: [Less repetition «](./TeachTheComputer-repetition.md) Home: [Home](../README.md) diff --git a/Samples/Beginners/TeachTheComputer-repetition.md b/Samples/Beginners/TeachTheComputer-repetition.md deleted file mode 100644 index 47845926f..000000000 --- a/Samples/Beginners/TeachTheComputer-repetition.md +++ /dev/null @@ -1,29 +0,0 @@ -# Let the computer repeat tasks - -Your hand coded solution should look something like the following code: - -``` cs --region handcoded-answer --source-file .\myapp\PascalsTriangle.cs --project .\myapp\myapp.csproj -``` - -As you put together that solution, you likely found yourself copying, pasting, and modifying some of the code. That is often a great way to explore a problem and try different ways to solve it. Once you understand the problem and have found a good solution, you can improve the code so you don't copy and past similar statements. Instead, let the computer repeat those steps. - -You'll make these changes in two steps. In the first step, you'll make a collection for all the rows in the triangle. Instead of the variables `row0`, `row1`, and so on, declare an array of arrays like the following: - -```csharp -int[][] triangle = new int[5][]; -``` - -From that, you can replace any use of `row0` with `triangle[0]`, `row1` with `triangle[1]` and so on. You'll also remove the declarations of `int[]` before `row0`, `row1` and so on. The variable `triangle` already creates the definitions for those types. As you start replacing variables, you'll see red squiggles highlighting where you use the variables you're replacing. After those replacements, you can replace the code that writes the values with one loop: - -```csharp -foreach (int[] row in triangle) -{ - foreach (var item in row) - Console.Write(item + " "); - Console.WriteLine(); -} -``` - -Try to modify the sample at the top of the page using these instructions. Run when you don't have red squiggles under your code. If the output window turns red, you may have the wrong indices in your array. Once you've got this working, continue for a bit more changes. - -#### Next: [Build the triangle »](./TeachTheComputer-repetition-2.md) Previous: [Teach the computer «](./TeachTheComputer.md) Home: [Home](../README.md) diff --git a/Samples/Beginners/TeachTheComputer.md b/Samples/Beginners/TeachTheComputer.md deleted file mode 100644 index 6e94d56f5..000000000 --- a/Samples/Beginners/TeachTheComputer.md +++ /dev/null @@ -1,38 +0,0 @@ -# Teach the computer - -As you learn more about programming, you'll find that you can teach the computer to do more and more work for you. Let's use an example you may recognize from math class. Try this even if math isn't your favorite class. You'll learn to get the computer to do your work for you. - -Pascal's triangle is a visualization of math pattern. The first row is a single `1`. The following rows all have one more item than the previous. Each row is created by adding `1` as the first and last element. Each inner element is found by taking the sum of the two numbers above directly to the left and right. You can see this in the row `1 2 1`. The `2` is the sum of the two `1`s above. Similarly, the `1 3 3 1` row: The first `3` is from the `1 2` in the row above, and next `3` is from the `2 1` that ends the row above. - -```console - 1 - 1 1 - 1 2 1 - 1 3 3 1 - 1 4 6 4 1 - 1 5 10 10 5 1 - 1 6 15 20 15 6 1 - 1 7 21 35 35 21 7 1 - 1 8 28 56 70 56 28 8 1 - 1 9 36 84 126 126 84 36 9 1 - 1 10 45 120 210 252 210 120 45 10 1 - 1 11 55 165 330 462 462 330 165 55 11 1 - 1 12 66 220 495 792 924 792 495 220 66 12 1 - 1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1 - 1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1 - 1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1 - 1 16 120 560 1820 4368 8008 11440 12870 11440 8008 4368 1820 560 120 16 1 -``` - -Computing successive rows in the triangle is not hard. But, it's very repetitive. Computers are great at tasks like these. You can teach it! - -## Explore code - -A good way to teach the computer is to start by exploring the problem yourself. The first step is to create some simple code that generates the first few rows. Try the following code yourself. It uses some of the concepts you've already learned. This example creates an array for each row of elements. It initializes the elements in the array. The last part prints the values in each row. - -``` cs --region handcoded --source-file .\myapp\PascalsTriangle.cs --project .\myapp\myapp.csproj -``` - -The format of the output doesn't form a nice triangle yet. Let's concentrate on the values first. Try and add a couple more rows to the triangle. Copy and paste the `row2` line and add rows 3 and 4. Do the same with the `foreach` loops that write the values. - -#### Next: [Less repetition »](./TeachTheComputer-repetition.md) Home: [Home](../README.md) diff --git a/Samples/Beginners/myapp/PascalsTriangle.cs b/Samples/Beginners/myapp/PascalsTriangle.cs deleted file mode 100644 index f63612979..000000000 --- a/Samples/Beginners/myapp/PascalsTriangle.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; - -namespace myapp -{ - public class PascalsTriangle - { - public static void HardCoded() - { - #region handcoded - int[] row0 = new int[] { 1 }; - int[] row1 = new int[] { 1, 1 }; - int[] row2 = new int[] { 1, row1[0] + row1[1], 1 }; - Console.WriteLine(row0[0]); - - foreach (var item in row1) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row2) - Console.Write(item + " "); - Console.WriteLine(); - #endregion - } - - public static void MoreHardCoded() - { - #region handcoded-answer - int[] row0 = new int[] { 1 }; - int[] row1 = new int[] { 1, 1 }; - int[] row2 = new int[] { 1, row1[0] + row1[1], 1 }; - int[] row3 = new int[] { 1, row2[0] + row2[1], row2[1] + row2[2], 1 }; - int[] row4 = new int[] { 1, row3[0] + row3[1], row3[1] + row3[2], row3[2] + row3[3], 1 }; - Console.WriteLine(row0[0]); - - foreach (var item in row1) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row2) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row3) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row4) - Console.Write(item + " "); - Console.WriteLine(); - #endregion - } - - public static void ArraysOfArrays() - { - #region more-arrays - int[][] triangle = new int[5][]; - triangle[0] = new int[] { 1 }; - triangle[1] = new int[] { 1, 1 }; - triangle[2] = new int[] { 1, triangle[1][0] + triangle[1][1], 1 }; - triangle[3] = new int[] { 1, triangle[2][0] + triangle[2][1], triangle[2][1] + triangle[2][2], 1 }; - triangle[4] = new int[] { 1, triangle[3][0] + triangle[3][1], triangle[3][1] + triangle[3][2], triangle[3][2] + triangle[3][3], 1 }; - - foreach (int[] row in triangle) - { - foreach (var item in row) - Console.Write(item + " "); - Console.WriteLine(); - } - #endregion - } - - public static void InitInLoops() - { - #region initialize-in-loop - - int[][] triangle = new int[5][]; - for (int rowIndex = 0; rowIndex < 5; rowIndex++) - { - triangle[rowIndex] = new int[rowIndex + 1]; - triangle[rowIndex][0] = 1; - - for (int column = 1; column < rowIndex; column++) - triangle[rowIndex][column] = triangle[rowIndex - 1][column - 1] + triangle[rowIndex - 1][column]; - - triangle[rowIndex][rowIndex] = 1; - } - foreach (int[] row in triangle) - { - foreach (var item in row) - Console.Write(item + " "); - Console.WriteLine(); - } - #endregion - } - - public static void Formattings() - { - #region formatting - const int MaxRows = 5; - int[][] triangle = new int[MaxRows][]; - for (int rowIndex = 0; rowIndex < MaxRows; rowIndex++) - { - triangle[rowIndex] = new int[rowIndex + 1]; - triangle[rowIndex][0] = 1; - - for (int column = 1; column < rowIndex; column++) - triangle[rowIndex][column] = triangle[rowIndex - 1][column - 1] + triangle[rowIndex - 1][column]; - - triangle[rowIndex][rowIndex] = 1; - rowIndex++; - } - const int fieldWidth = 6; - foreach (int[] row in triangle) - { - int indent = (MaxRows - row.Length) * (fieldWidth / 2); - Console.Write(new string(' ', indent)); - foreach (var item in row) - Console.Write($"{item, fieldWidth}"); - Console.WriteLine(); - } - #endregion - } - } -} diff --git a/Samples/Beginners/myapp/Program.cs b/Samples/Beginners/myapp/Program.cs deleted file mode 100644 index 1c71a64c9..000000000 --- a/Samples/Beginners/myapp/Program.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -namespace myapp -{ - class Program - { - static void Main(string region = null, - string session = null, - string package = null, - string project = null, - string[] args = null) - { - switch(region) - { - case "intro": - Intro(); - break; - case "strings": - Strings(); - break; - case "variables": - Variables(); - break; - case "interpolation": - Interpolation(); - break; - case "methods": - Methods(); - break; - case "collections": - Collections(); - break; - case "handcoded": - PascalsTriangle.HardCoded(); - break; - case "handcoded-answer": - PascalsTriangle.MoreHardCoded(); - break; - case "more-arrays": - PascalsTriangle.ArraysOfArrays(); - break; - case "initialize-in-loop": - PascalsTriangle.InitInLoops(); - break; - } - } - public static void Intro() - { - #region intro - Console.WriteLine("Hello World!"); - #endregion - } - public static void Strings() - { - #region strings - Console.WriteLine("Hello Rain"); - #endregion - } - public static void Variables() - { - #region variables - var name = "Rain"; - Console.WriteLine("Hello " + name + "!"); - #endregion - } - - public static void Interpolation() - { - #region interpolation - var name = "Rain"; - Console.WriteLine($"Hello {name}!"); - #endregion - } - public static void Methods() - { - #region methods - var name ="Rain"; - Console.WriteLine($"Hello {name.ToUpper()}!"); - #endregion - } - public static void Collections() - { - #region collections - var names = new List { "Rain", "Sage", "Lee" }; - foreach (var name in names) - { - Console.WriteLine($"Hello {name.ToUpper()}!"); - } - #endregion - } - - public static void PascalsTriangleHardCoded() - { - #region handcoded - int[] row0 = new int[] { 1 }; - int[] row1 = new int[] { 1, 1 }; - int[] row2 = new int[] { 1, row1[0] + row1[1], 1 }; - Console.WriteLine(row0[0]); - - foreach (var item in row1) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row2) - Console.Write(item + " "); - Console.WriteLine(); - #endregion - } - - public static void PascalsTriangleMoreHardCoded() - { - #region handcoded-answer - int[] row0 = new int[] { 1 }; - int[] row1 = new int[] { 1, 1 }; - int[] row2 = new int[] { 1, row1[0] + row1[1], 1 }; - int[] row3 = new int[] { 1, row2[0] + row2[1], row2[1] + row2[2], 1 }; - int[] row4 = new int[] { 1, row3[0] + row3[1], row3[1] + row3[2], row3[2] + row3[3], 1 }; - Console.WriteLine(row0[0]); - - foreach (var item in row1) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row2) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row3) - Console.Write(item + " "); - Console.WriteLine(); - - foreach (var item in row4) - Console.Write(item + " "); - Console.WriteLine(); - #endregion - } - - public static void PascalsTriangleArraysOfArrays() - { - #region more-arrays - int[][] triangle = new int[5][]; - triangle[0] = new int[] { 1 }; - triangle[1] = new int[] { 1, 1 }; - triangle[2] = new int[] { 1, triangle[1][0] + triangle[1][1], 1 }; - triangle[3] = new int[] { 1, triangle[2][0] + triangle[2][1], triangle[2][1] + triangle[2][2], 1 }; - triangle[4] = new int[] { 1, triangle[3][0] + triangle[3][1], triangle[3][1] + triangle[3][2], triangle[3][2] + triangle[3][3], 1 }; - - foreach (int[] row in triangle) - { - foreach (var item in row) - Console.Write(item + " "); - Console.WriteLine(); - } - #endregion - } - - public static void PascalsTriangleInitInLoops() - { - #region initialize-in-loop - int[][] triangle = new int[5][]; - for (int rowIndex = 0; rowIndex < 5; rowIndex++) - { - triangle[rowIndex] = new int[rowIndex + 1]; - triangle[rowIndex][0] = 1; - - for (int column = 1; column < rowIndex; column++) - triangle[rowIndex][column] = triangle[rowIndex - 1][column - 1] + triangle[rowIndex - 1][column]; - - triangle[rowIndex][rowIndex] = 1; - } - foreach (int[] row in triangle) - { - foreach (var item in row) - Console.Write(item + " "); - Console.WriteLine(); - } - #endregion - } - - public static void PascalsTriangleFormattings() - { - #region formatting - const int MaxRows = 5; - int[][] triangle = new int[MaxRows][]; - for (int rowIndex = 0; rowIndex < MaxRows; rowIndex++) - { - triangle[rowIndex] = new int[rowIndex + 1]; - triangle[rowIndex][0] = 1; - - for (int column = 1; column < rowIndex; column++) - triangle[rowIndex][column] = triangle[rowIndex - 1][column - 1] + triangle[rowIndex - 1][column]; - - triangle[rowIndex][rowIndex] = 1; - rowIndex++; - } - const int fieldWidth = 6; - foreach (int[] row in triangle) - { - int indent = (MaxRows - row.Length) * (fieldWidth / 2); - Console.Write(new string(' ', indent)); - foreach (var item in row) - Console.Write($"{item, fieldWidth}"); - Console.WriteLine(); - } - #endregion - } - } -} diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll deleted file mode 100644 index 5623e5d35..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll deleted file mode 100644 index e71f0a022..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.dll b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.dll deleted file mode 100644 index be7bbe237..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/System.CommandLine.dll and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.deps.json b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.deps.json deleted file mode 100644 index 0bab54aa7..000000000 --- a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.deps.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v3.0", - "signature": "7253968ba1d4afda34901bfaac1dcef18a086b91" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v3.0": { - "myapp/1.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0-preview3.19128.7", - "Microsoft.WindowsDesktop.App": "3.0.0-preview3-27504-2", - "System.CommandLine.DragonFruit": "0.2.0-alpha.19176.2", - "System.CommandLine.Experimental": "0.2.0-alpha.19176.2", - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": "3.0.0-preview3-27503-5" - }, - "runtime": { - "myapp.dll": {} - } - }, - "Microsoft.CSharp/4.4.1": {}, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": {}, - "Microsoft.NETCore.Targets/2.0.0": {}, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0-preview3.19128.7", - "Microsoft.NETCore.Targets": "2.0.0" - } - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": {}, - "System.CommandLine.DragonFruit/0.2.0-alpha.19176.2": { - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19176.2", - "System.CommandLine.Rendering": "0.2.0-alpha.19176.2" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17602" - } - } - }, - "System.CommandLine.Experimental/0.2.0-alpha.19176.2": { - "dependencies": { - "Microsoft.CSharp": "4.4.1" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17602" - } - } - }, - "System.CommandLine.Rendering/0.2.0-alpha.19176.2": { - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19176.2" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17602" - } - } - } - } - }, - "libraries": { - "myapp/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.CSharp/4.4.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-A5hI3gk6WpcBI0QGZY6/d5CCaYUxJgi7iENn1uYEng+Olo8RfI5ReGVkjXjeu3VR3srLvVYREATXa2M0X7FYJA==", - "path": "microsoft.csharp/4.4.1", - "hashPath": "microsoft.csharp.4.4.1.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "type": "package", - "serviceable": true, - "sha512": "sha512-4/k52fBHvM1RNPoBMik0Y3wIovsUGaTadNGH75iLGjQCJPRq2UBivEkhYrYkIFnu+K9kTYJcmXCDF6SFzMBrqA==", - "path": "microsoft.netcore.platforms/3.0.0-preview3.19128.7", - "hashPath": "microsoft.netcore.platforms.3.0.0-preview3.19128.7.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-odP/tJj1z6GylFpNo7pMtbd/xQgTC3Ex2If63dRTL38bBNMwsBnJ+RceUIyHdRBC0oik/3NehYT+oECwBhIM3Q==", - "path": "microsoft.netcore.targets/2.0.0", - "hashPath": "microsoft.netcore.targets.2.0.0.nupkg.sha512" - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t7Iw6nkx03aOXgZ7L2ZJLN7hGY7FlUaxIyH6UbGhjHCJABxfV8Bx3xOe6E63xa1HVeoccCAdNPctTX2s2zj5SQ==", - "path": "microsoft.windowsdesktop.app/3.0.0-preview3-27504-2", - "hashPath": "microsoft.windowsdesktop.app.3.0.0-preview3-27504-2.nupkg.sha512" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9xq1NTCrWg3A4/6K/COActt25NJaw1a6bx0CTIkgbYv2TkpveKXZCCSFyDnT5Ff9nBvZkBq4Y4r/tZNIU5T8pg==", - "path": "runtime.win-x64.microsoft.netcore.dotnetapphost/3.0.0-preview3-27503-5", - "hashPath": "runtime.win-x64.microsoft.netcore.dotnetapphost.3.0.0-preview3-27503-5.nupkg.sha512" - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19176.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t+XZMDNM6G2ntr1V5v6+ohieoxvgaPQ5Ua4vwf1L4ezDtOsYEJuF6DYye9MNn/St7DxdPrGfk2nZ+j93yRmYeA==", - "path": "system.commandline.dragonfruit/0.2.0-alpha.19176.2", - "hashPath": "system.commandline.dragonfruit.0.2.0-alpha.19176.2.nupkg.sha512" - }, - "System.CommandLine.Experimental/0.2.0-alpha.19176.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-QYV7dkj6eb1xWJiHHS/hN1eOKL9Z/IqDyl/nsulzY78I+Upcdy8EibbI9mg8sz0ePywGKOiXSePrBjkQSiQe7w==", - "path": "system.commandline.experimental/0.2.0-alpha.19176.2", - "hashPath": "system.commandline.experimental.0.2.0-alpha.19176.2.nupkg.sha512" - }, - "System.CommandLine.Rendering/0.2.0-alpha.19176.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-F0F8OYy/GblG4aU1U45I+6re3Fs/hiMMrAiJSyhNf9ifBfpkllbqdJHnlvvgH1dNNlg5egh6p+mn9azFg1B9ww==", - "path": "system.commandline.rendering/0.2.0-alpha.19176.2", - "hashPath": "system.commandline.rendering.0.2.0-alpha.19176.2.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.dll b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.dll deleted file mode 100644 index 7386a0cd4..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.dll and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.exe b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.exe deleted file mode 100644 index 59eb46fa7..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.exe and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.pdb b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.pdb deleted file mode 100644 index aa06c1c47..000000000 Binary files a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.pdb and /dev/null differ diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.dev.json b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.dev.json deleted file mode 100644 index 22852d52d..000000000 --- a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "additionalProbingPaths": [ - "C:\\Users\\Maria Naggaga\\.dotnet\\store\\|arch|\\|tfm|", - "C:\\Users\\Maria Naggaga\\.nuget\\packages", - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ] - } -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.json b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.json deleted file mode 100644 index b6f483737..000000000 --- a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp3.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "3.0.0-preview3-27503-5" - } - } -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.xml b/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.xml deleted file mode 100644 index 002d37e54..000000000 --- a/Samples/Beginners/myapp/bin/Debug/netcoreapp3.0/myapp.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - myapp - - - - diff --git a/Samples/Beginners/myapp/myapp.csproj b/Samples/Beginners/myapp/myapp.csproj deleted file mode 100644 index b282444b6..000000000 --- a/Samples/Beginners/myapp/myapp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - netcoreapp3.0 - - - - - - - - diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfo.cs b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfo.cs deleted file mode 100644 index 848d91656..000000000 --- a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Generated by the MSBuild WriteCodeFragment class. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("myapp")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] -[assembly: System.Reflection.AssemblyProductAttribute("myapp")] -[assembly: System.Reflection.AssemblyTitleAttribute("myapp")] -[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfoInputs.cache b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfoInputs.cache deleted file mode 100644 index 2389e8b4d..000000000 --- a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -069396df9b5556c195afa238c5388b312a3357bb diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.Program.g.cs b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.Program.g.cs deleted file mode 100644 index e5263d9af..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.Program.g.cs and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.assets.cache b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.assets.cache deleted file mode 100644 index e828a16fc..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.assets.cache and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.CopyComplete b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.CopyComplete deleted file mode 100644 index e69de29bb..000000000 diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.CoreCompileInputs.cache b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.CoreCompileInputs.cache deleted file mode 100644 index 33efa4794..000000000 --- a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.CoreCompileInputs.cache +++ /dev/null @@ -1 +0,0 @@ -55b40a997059a6b6ce19d5a1931368940e79c38d diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.FileListAbsolute.txt b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.FileListAbsolute.txt deleted file mode 100644 index 1633a88d1..000000000 --- a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csproj.FileListAbsolute.txt +++ /dev/null @@ -1,55 +0,0 @@ -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.exe -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.deps.json -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.json -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.dev.json -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.dll -C:\dev\tdn_samples\tdnsamples\myapp\bin\Debug\netcoreapp3.0\myapp.pdb -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.csprojAssemblyReference.cache -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CoreCompileInputs.cache -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfoInputs.cache -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfo.cs -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.dll -C:\dev\tdn_samples\tdnsamples\myapp\obj\Debug\netcoreapp3.0\myapp.pdb -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\bin\Debug\netcoreapp3.0\myapp.exe -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\bin\Debug\netcoreapp3.0\myapp.deps.json -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.json -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.dev.json -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CoreCompileInputs.cache -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfoInputs.cache -C:\dev\tdn_samples\tdnsamples\Intro_to_C#\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfo.cs -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.exe -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.deps.json -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.json -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.dev.json -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CoreCompileInputs.cache -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfoInputs.cache -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfo.cs -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.dll -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.pdb -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.xml -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.DragonFruit.dll -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.dll -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.Rendering.dll -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csprojAssemblyReference.cache -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CopyComplete -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.dll -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.xml -C:\dev\tdn_samples\tdnsamples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.pdb -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.exe -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.deps.json -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.json -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.runtimeconfig.dev.json -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.xml -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CoreCompileInputs.cache -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfoInputs.cache -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.AssemblyInfo.cs -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.dll -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.xml -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.pdb -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.dll -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\myapp.pdb -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.DragonFruit.dll -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.dll -C:\dev\try\try\Samples\Beginners\myapp\bin\Debug\netcoreapp3.0\System.CommandLine.Rendering.dll -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csprojAssemblyReference.cache -C:\dev\try\try\Samples\Beginners\myapp\obj\Debug\netcoreapp3.0\myapp.csproj.CopyComplete diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csprojAssemblyReference.cache b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csprojAssemblyReference.cache deleted file mode 100644 index ec6d0096c..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.csprojAssemblyReference.cache and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.dll b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.dll deleted file mode 100644 index 7386a0cd4..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.dll and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.exe b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.exe deleted file mode 100644 index 59eb46fa7..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.exe and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.pdb b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.pdb deleted file mode 100644 index aa06c1c47..000000000 Binary files a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.pdb and /dev/null differ diff --git a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.xml b/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.xml deleted file mode 100644 index 002d37e54..000000000 --- a/Samples/Beginners/myapp/obj/Debug/netcoreapp3.0/myapp.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - myapp - - - - diff --git a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.cache b/Samples/Beginners/myapp/obj/myapp.csproj.nuget.cache deleted file mode 100644 index 26f8ec176..000000000 --- a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.cache +++ /dev/null @@ -1,5 +0,0 @@ -{ - "version": 1, - "dgSpecHash": "Sr5EDDUsE9zf8bdiIUEYTKdxFfUiWhiiY3nFrtcCYz6RwlvLL90dpYmXq+1TVYT5pk/yszUaxyN3bBGyT0sNlg==", - "success": true -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.dgspec.json b/Samples/Beginners/myapp/obj/myapp.csproj.nuget.dgspec.json deleted file mode 100644 index 331105be0..000000000 --- a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.dgspec.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "format": 1, - "restore": { - "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj": {} - }, - "projects": { - "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj", - "projectName": "myapp", - "projectPath": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj", - "packagesPath": "C:\\Users\\Maria Naggaga\\.nuget\\packages\\", - "outputPath": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ], - "configFilePaths": [ - "C:\\dev\\NuGet.Config", - "C:\\Users\\Maria Naggaga\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "netcoreapp3.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json": {}, - "https://msazure.pkgs.visualstudio.com/_packaging/MLS/nuget/v3/index.json": {} - }, - "frameworks": { - "netcoreapp3.0": { - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - } - }, - "frameworks": { - "netcoreapp3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": { - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3.19128.7, )", - "autoReferenced": true - }, - "Microsoft.WindowsDesktop.App": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27504-2, )", - "autoReferenced": true - }, - "System.CommandLine.DragonFruit": { - "target": "Package", - "version": "[0.2.0-alpha.19176.2, )" - }, - "System.CommandLine.Experimental": { - "target": "Package", - "version": "[0.2.0-alpha.19176.2, )" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27503-5, )", - "autoReferenced": true - } - }, - "imports": [ - "net461" - ], - "assetTargetFallback": true, - "warn": true - } - } - } - } -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.props b/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.props deleted file mode 100644 index 8b97683f3..000000000 --- a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.props +++ /dev/null @@ -1,18 +0,0 @@ - - - - True - NuGet - C:\dev\try\try\Samples\Beginners\myapp\obj\project.assets.json - $(UserProfile)\.nuget\packages\ - C:\Users\Maria Naggaga\.nuget\packages\;C:\Program Files\dotnet\sdk\NuGetFallbackFolder - PackageReference - 5.0.0 - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - \ No newline at end of file diff --git a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.targets b/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.targets deleted file mode 100644 index f58b43fee..000000000 --- a/Samples/Beginners/myapp/obj/myapp.csproj.nuget.g.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - \ No newline at end of file diff --git a/Samples/Beginners/myapp/obj/project.assets.json b/Samples/Beginners/myapp/obj/project.assets.json deleted file mode 100644 index 40b27a216..000000000 --- a/Samples/Beginners/myapp/obj/project.assets.json +++ /dev/null @@ -1,412 +0,0 @@ -{ - "version": 3, - "targets": { - ".NETCoreApp,Version=v3.0": { - "Microsoft.CSharp/4.4.1": { - "type": "package", - "compile": { - "ref/netcoreapp2.0/_._": {} - }, - "runtime": { - "lib/netcoreapp2.0/_._": {} - } - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "type": "package", - "compile": { - "lib/netstandard1.0/_._": {} - }, - "runtime": { - "lib/netstandard1.0/_._": {} - } - }, - "Microsoft.NETCore.Targets/2.0.0": { - "type": "package", - "compile": { - "lib/netstandard1.0/_._": {} - }, - "runtime": { - "lib/netstandard1.0/_._": {} - } - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "type": "package", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.0.0", - "Microsoft.NETCore.Targets": "2.0.0" - }, - "compile": { - "ref/netcoreapp3.0/_._": {} - }, - "build": { - "build/netcoreapp3.0/_._": {} - } - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "type": "package", - "runtimeTargets": { - "runtimes/win-x64/native/_._": { - "assetType": "native", - "rid": "win-x64" - } - } - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19176.2": { - "type": "package", - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19176.2", - "System.CommandLine.Rendering": "0.2.0-alpha.19176.2" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": {} - }, - "build": { - "build/netstandard2.0/System.CommandLine.DragonFruit.props": {}, - "build/netstandard2.0/System.CommandLine.DragonFruit.targets": {} - } - }, - "System.CommandLine.Experimental/0.2.0-alpha.19176.2": { - "type": "package", - "dependencies": { - "Microsoft.CSharp": "4.4.1" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.dll": {} - } - }, - "System.CommandLine.Rendering/0.2.0-alpha.19176.2": { - "type": "package", - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19176.2" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": {} - } - } - } - }, - "libraries": { - "Microsoft.CSharp/4.4.1": { - "sha512": "A5hI3gk6WpcBI0QGZY6/d5CCaYUxJgi7iENn1uYEng+Olo8RfI5ReGVkjXjeu3VR3srLvVYREATXa2M0X7FYJA==", - "type": "package", - "path": "microsoft.csharp/4.4.1", - "files": [ - ".nupkg.metadata", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/netcore50/Microsoft.CSharp.dll", - "lib/netcoreapp2.0/_._", - "lib/netstandard1.3/Microsoft.CSharp.dll", - "lib/netstandard2.0/Microsoft.CSharp.dll", - "lib/portable-net45+win8+wp8+wpa81/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "lib/xamarintvos10/_._", - "lib/xamarinwatchos10/_._", - "microsoft.csharp.4.4.1.nupkg.sha512", - "microsoft.csharp.nuspec", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/net45/_._", - "ref/netcore50/Microsoft.CSharp.dll", - "ref/netcore50/Microsoft.CSharp.xml", - "ref/netcore50/de/Microsoft.CSharp.xml", - "ref/netcore50/es/Microsoft.CSharp.xml", - "ref/netcore50/fr/Microsoft.CSharp.xml", - "ref/netcore50/it/Microsoft.CSharp.xml", - "ref/netcore50/ja/Microsoft.CSharp.xml", - "ref/netcore50/ko/Microsoft.CSharp.xml", - "ref/netcore50/ru/Microsoft.CSharp.xml", - "ref/netcore50/zh-hans/Microsoft.CSharp.xml", - "ref/netcore50/zh-hant/Microsoft.CSharp.xml", - "ref/netcoreapp2.0/_._", - "ref/netstandard1.0/Microsoft.CSharp.dll", - "ref/netstandard1.0/Microsoft.CSharp.xml", - "ref/netstandard1.0/de/Microsoft.CSharp.xml", - "ref/netstandard1.0/es/Microsoft.CSharp.xml", - "ref/netstandard1.0/fr/Microsoft.CSharp.xml", - "ref/netstandard1.0/it/Microsoft.CSharp.xml", - "ref/netstandard1.0/ja/Microsoft.CSharp.xml", - "ref/netstandard1.0/ko/Microsoft.CSharp.xml", - "ref/netstandard1.0/ru/Microsoft.CSharp.xml", - "ref/netstandard1.0/zh-hans/Microsoft.CSharp.xml", - "ref/netstandard1.0/zh-hant/Microsoft.CSharp.xml", - "ref/netstandard2.0/Microsoft.CSharp.dll", - "ref/netstandard2.0/Microsoft.CSharp.xml", - "ref/portable-net45+win8+wp8+wpa81/_._", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._", - "ref/xamarintvos10/_._", - "ref/xamarinwatchos10/_._", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "sha512": "4/k52fBHvM1RNPoBMik0Y3wIovsUGaTadNGH75iLGjQCJPRq2UBivEkhYrYkIFnu+K9kTYJcmXCDF6SFzMBrqA==", - "type": "package", - "path": "microsoft.netcore.platforms/3.0.0-preview3.19128.7", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/netstandard1.0/_._", - "microsoft.netcore.platforms.3.0.0-preview3.19128.7.nupkg.sha512", - "microsoft.netcore.platforms.nuspec", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.NETCore.Targets/2.0.0": { - "sha512": "odP/tJj1z6GylFpNo7pMtbd/xQgTC3Ex2If63dRTL38bBNMwsBnJ+RceUIyHdRBC0oik/3NehYT+oECwBhIM3Q==", - "type": "package", - "path": "microsoft.netcore.targets/2.0.0", - "files": [ - ".nupkg.metadata", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/netstandard1.0/_._", - "microsoft.netcore.targets.2.0.0.nupkg.sha512", - "microsoft.netcore.targets.nuspec", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "sha512": "t7Iw6nkx03aOXgZ7L2ZJLN7hGY7FlUaxIyH6UbGhjHCJABxfV8Bx3xOe6E63xa1HVeoccCAdNPctTX2s2zj5SQ==", - "type": "package", - "path": "microsoft.windowsdesktop.app/3.0.0-preview3-27504-2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "build/netcoreapp3.0/Microsoft.WindowsDesktop.App.targets", - "build/netcoreapp3.0/ref-mscorlib/System.ComponentModel.dll", - "build/netcoreapp3.0/ref-mscorlib/System.Runtime.CompilerServices.VisualC.dll", - "build/netcoreapp3.0/ref-mscorlib/System.Runtime.dll", - "build/netcoreapp3.0/ref-mscorlib/mscorlib.dll", - "microsoft.windowsdesktop.app.3.0.0-preview3-27504-2.nupkg.sha512", - "microsoft.windowsdesktop.app.nuspec", - "ref/netcoreapp3.0/Accessibility.dll", - "ref/netcoreapp3.0/Microsoft.Win32.Registry.dll", - "ref/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll", - "ref/netcoreapp3.0/PresentationCore-CommonResources.dll", - "ref/netcoreapp3.0/PresentationCore.dll", - "ref/netcoreapp3.0/PresentationFramework.Aero.dll", - "ref/netcoreapp3.0/PresentationFramework.Aero2.dll", - "ref/netcoreapp3.0/PresentationFramework.AeroLite.dll", - "ref/netcoreapp3.0/PresentationFramework.Classic.dll", - "ref/netcoreapp3.0/PresentationFramework.Luna.dll", - "ref/netcoreapp3.0/PresentationFramework.Royale.dll", - "ref/netcoreapp3.0/PresentationFramework.dll", - "ref/netcoreapp3.0/PresentationUI.dll", - "ref/netcoreapp3.0/ReachFramework.dll", - "ref/netcoreapp3.0/System.CodeDom.dll", - "ref/netcoreapp3.0/System.Configuration.ConfigurationManager.dll", - "ref/netcoreapp3.0/System.Design.dll", - "ref/netcoreapp3.0/System.Drawing.Common.dll", - "ref/netcoreapp3.0/System.Drawing.Design.dll", - "ref/netcoreapp3.0/System.Drawing.dll", - "ref/netcoreapp3.0/System.Printing.dll", - "ref/netcoreapp3.0/System.Security.AccessControl.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Cng.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Pkcs.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.ProtectedData.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Xml.dll", - "ref/netcoreapp3.0/System.Security.Permissions.dll", - "ref/netcoreapp3.0/System.Security.Principal.Windows.dll", - "ref/netcoreapp3.0/System.Windows.Controls.Ribbon.dll", - "ref/netcoreapp3.0/System.Windows.Extensions.dll", - "ref/netcoreapp3.0/System.Windows.Forms.Design.Editors.dll", - "ref/netcoreapp3.0/System.Windows.Forms.Design.dll", - "ref/netcoreapp3.0/System.Windows.Forms.dll", - "ref/netcoreapp3.0/System.Windows.Input.Manipulations.dll", - "ref/netcoreapp3.0/System.Windows.Presentation.dll", - "ref/netcoreapp3.0/System.Xaml.dll", - "ref/netcoreapp3.0/UIAutomationClient.dll", - "ref/netcoreapp3.0/UIAutomationClientSideProviders.dll", - "ref/netcoreapp3.0/UIAutomationProvider.dll", - "ref/netcoreapp3.0/UIAutomationTypes.dll", - "ref/netcoreapp3.0/WindowsBase.dll", - "ref/netcoreapp3.0/WindowsFormsIntegration.dll", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "sha512": "9xq1NTCrWg3A4/6K/COActt25NJaw1a6bx0CTIkgbYv2TkpveKXZCCSFyDnT5Ff9nBvZkBq4Y4r/tZNIU5T8pg==", - "type": "package", - "path": "runtime.win-x64.microsoft.netcore.dotnetapphost/3.0.0-preview3-27503-5", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "runtime.win-x64.microsoft.netcore.dotnetapphost.3.0.0-preview3-27503-5.nupkg.sha512", - "runtime.win-x64.microsoft.netcore.dotnetapphost.nuspec", - "runtimes/win-x64/native/apphost.exe", - "runtimes/win-x64/native/comhost.dll", - "version.txt" - ] - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19176.2": { - "sha512": "t+XZMDNM6G2ntr1V5v6+ohieoxvgaPQ5Ua4vwf1L4ezDtOsYEJuF6DYye9MNn/St7DxdPrGfk2nZ+j93yRmYeA==", - "type": "package", - "path": "system.commandline.dragonfruit/0.2.0-alpha.19176.2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "build/netstandard2.0/System.CommandLine.DragonFruit.props", - "build/netstandard2.0/System.CommandLine.DragonFruit.targets", - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll", - "lib/netstandard2.0/System.CommandLine.DragonFruit.pdb", - "system.commandline.dragonfruit.0.2.0-alpha.19176.2.nupkg.sha512", - "system.commandline.dragonfruit.nuspec" - ] - }, - "System.CommandLine.Experimental/0.2.0-alpha.19176.2": { - "sha512": "QYV7dkj6eb1xWJiHHS/hN1eOKL9Z/IqDyl/nsulzY78I+Upcdy8EibbI9mg8sz0ePywGKOiXSePrBjkQSiQe7w==", - "type": "package", - "path": "system.commandline.experimental/0.2.0-alpha.19176.2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/netstandard2.0/System.CommandLine.dll", - "lib/netstandard2.0/System.CommandLine.pdb", - "system.commandline.experimental.0.2.0-alpha.19176.2.nupkg.sha512", - "system.commandline.experimental.nuspec" - ] - }, - "System.CommandLine.Rendering/0.2.0-alpha.19176.2": { - "sha512": "F0F8OYy/GblG4aU1U45I+6re3Fs/hiMMrAiJSyhNf9ifBfpkllbqdJHnlvvgH1dNNlg5egh6p+mn9azFg1B9ww==", - "type": "package", - "path": "system.commandline.rendering/0.2.0-alpha.19176.2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/netstandard2.0/System.CommandLine.Rendering.dll", - "lib/netstandard2.0/System.CommandLine.Rendering.pdb", - "system.commandline.rendering.0.2.0-alpha.19176.2.nupkg.sha512", - "system.commandline.rendering.nuspec" - ] - } - }, - "projectFileDependencyGroups": { - ".NETCoreApp,Version=v3.0": [ - "Microsoft.NETCore.Platforms >= 3.0.0-preview3.19128.7", - "Microsoft.WindowsDesktop.App >= 3.0.0-preview3-27504-2", - "System.CommandLine.DragonFruit >= 0.2.0-alpha.19176.2", - "System.CommandLine.Experimental >= 0.2.0-alpha.19176.2", - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost >= 3.0.0-preview3-27503-5" - ] - }, - "packageFolders": { - "C:\\Users\\Maria Naggaga\\.nuget\\packages\\": {}, - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder": {} - }, - "project": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj", - "projectName": "myapp", - "projectPath": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\myapp.csproj", - "packagesPath": "C:\\Users\\Maria Naggaga\\.nuget\\packages\\", - "outputPath": "C:\\dev\\try\\try\\Samples\\Beginners\\myapp\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ], - "configFilePaths": [ - "C:\\dev\\NuGet.Config", - "C:\\Users\\Maria Naggaga\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "netcoreapp3.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json": {}, - "https://msazure.pkgs.visualstudio.com/_packaging/MLS/nuget/v3/index.json": {} - }, - "frameworks": { - "netcoreapp3.0": { - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - } - }, - "frameworks": { - "netcoreapp3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": { - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3.19128.7, )", - "autoReferenced": true - }, - "Microsoft.WindowsDesktop.App": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27504-2, )", - "autoReferenced": true - }, - "System.CommandLine.DragonFruit": { - "target": "Package", - "version": "[0.2.0-alpha.19176.2, )" - }, - "System.CommandLine.Experimental": { - "target": "Package", - "version": "[0.2.0-alpha.19176.2, )" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27503-5, )", - "autoReferenced": true - } - }, - "imports": [ - "net461" - ], - "assetTargetFallback": true, - "warn": true - } - } - } -} \ No newline at end of file diff --git a/Samples/Beginners/myapp/package_fullBuild.binlog b/Samples/Beginners/myapp/package_fullBuild.binlog deleted file mode 100644 index 7ca904c8e..000000000 Binary files a/Samples/Beginners/myapp/package_fullBuild.binlog and /dev/null differ diff --git a/Samples/Readme.md b/Samples/Readme.md deleted file mode 100644 index 982739af5..000000000 --- a/Samples/Readme.md +++ /dev/null @@ -1,14 +0,0 @@ -# Try .NET Enabled Samples -![dotnet try Enabled](https://img.shields.io/badge/Try_.NET-Enabled-501078.svg) - -*Please read the [Try .NET quick setup guide](Setup.md) before starting the tutorial below.* - -

- -

-

Try .NET Samples

- -### Table of Content -- [Introduction Programming with C#](Beginners/HelloWorld.md) -- [Pascals Triangle](Beginners/TeachTheComputer.md) -- [A tour of C#8](csharp8/index.md) diff --git a/Samples/Setup.md b/Samples/Setup.md deleted file mode 100644 index 19fa41e07..000000000 --- a/Samples/Setup.md +++ /dev/null @@ -1,15 +0,0 @@ -![dotnet try Enabled](https://img.shields.io/badge/Try_.NET-Enabled-501078.svg) - -### How do get started with a Try .NET enabled repo? -- Clone this repo -- Install .NET Core SDK [3.0](https://dotnet.microsoft.com/download/dotnet-core/3.0) and [2.1](https://dotnet.microsoft.com/download/dotnet-core/2.1) currently dotnet try global tool targets 2.1. -- Go to your terminal -- Install the [Try .NET tools]() -`dotnet tool install -g dotnet-try` -- Navigate to the Samples directory of this repository and, type the following `dotnet try`. -![dotnet try](https://user-images.githubusercontent.com/2546640/57164943-ab35f080-6dc3-11e9-8230-ee521e00e428.gif) -- This will launch the browser. - -![dotnet try - _Readme md (2)](https://user-images.githubusercontent.com/2546640/57165217-737b7880-6dc4-11e9-8b4e-0e70966ac03d.gif) - -- Now, you can read the documentation and run code in one place. diff --git a/Samples/csharp8/ExploreCsharpEight/AsyncStreams.cs b/Samples/csharp8/ExploreCsharpEight/AsyncStreams.cs deleted file mode 100644 index 91e4abe97..000000000 --- a/Samples/csharp8/ExploreCsharpEight/AsyncStreams.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace ExploreCsharpEight -{ - class AsyncStreams - { - #region AsyncStreams_Declare - internal async IAsyncEnumerable GenerateSequence() - { - for (int i = 0; i < 20; i++) - { - // every 3 elements, wait 2 seconds: - if (i % 3 == 0) - await Task.Delay(3000); - yield return i; - } - } - #endregion - - internal async Task ConsumeStream() - { - #region AsyncStreams_Consume - await foreach (var number in GenerateSequence()) - { - Console.WriteLine($"The time is {DateTime.Now:hh:mm:ss}. Retrieved {number}"); - } - #endregion - - return 0; - } - - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/ExploreCsharpEight.csproj b/Samples/csharp8/ExploreCsharpEight/ExploreCsharpEight.csproj deleted file mode 100644 index ad8f5c904..000000000 --- a/Samples/csharp8/ExploreCsharpEight/ExploreCsharpEight.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - netcoreapp3.0 - 8.0 - - - - - - - - diff --git a/Samples/csharp8/ExploreCsharpEight/ExternalSystems.cs b/Samples/csharp8/ExploreCsharpEight/ExternalSystems.cs deleted file mode 100644 index ca71e16df..000000000 --- a/Samples/csharp8/ExploreCsharpEight/ExternalSystems.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace ConsumerVehicleRegistration -{ - public class Car - { - public int Passengers { get; set; } - } -} - -namespace CommercialRegistration -{ - public class DeliveryTruck - { - public int GrossWeightClass { get; set; } - } -} - -namespace LiveryRegistration -{ - public class Taxi - { - public int Fares { get; set; } - } - - public class Bus - { - public int Capacity { get; set; } - public int Riders { get; set; } - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/IndicesAndRanges.cs b/Samples/csharp8/ExploreCsharpEight/IndicesAndRanges.cs deleted file mode 100644 index 72667e352..000000000 --- a/Samples/csharp8/ExploreCsharpEight/IndicesAndRanges.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Linq; - -namespace ExploreCsharpEight -{ - class IndicesAndRanges - { - // TODO: replace in IndicesAndRanges.md when non-editable blocks are supported. - #region IndicesAndRanges_Initialization - private string[] words = new string[] - { - // index from start index from end - "The", // 0 ^9 - "quick", // 1 ^8 - "brown", // 2 ^7 - "fox", // 3 ^6 - "jumped", // 4 ^5 - "over", // 5 ^4 - "the", // 6 ^3 - "lazy", // 7 ^2 - "dog" // 8 ^1 - }; - #endregion - - internal int Syntax_LastIndex() - { - #region IndicesAndRanges_LastIndex - Console.WriteLine($"The last word is {words[^1]}"); - #endregion - return 0; - } - - internal int Syntax_Range() - { - #region IndicesAndRanges_Range - var quickBrownFox = words[1..4]; - foreach (var word in quickBrownFox) - Console.Write($"< {word} >"); - Console.WriteLine(); - #endregion - return 0; - } - - internal int Syntax_LastRange() - { - #region IndicesAndRanges_LastRange - var lazyDog = words[^2..^0]; - foreach (var word in lazyDog) - Console.Write($"< {word} >"); - Console.WriteLine(); - #endregion - return 0; - } - - internal int Syntax_PartialRange() - { - #region IndicesAndRanges_PartialRanges - var allWords = words[..]; // contains "The" through "dog". - var firstPhrase = words[..4]; // contains "The" through "fox" - var lastPhrase = words[6..]; // contains "the, "lazy" and "dog" - foreach (var word in allWords) - Console.Write($"< {word} >"); - Console.WriteLine(); - foreach (var word in firstPhrase) - Console.Write($"< {word} >"); - Console.WriteLine(); - foreach (var word in lastPhrase) - Console.Write($"< {word} >"); - Console.WriteLine(); - #endregion - return 0; - } - - internal int Syntax_IndexRangeType() - { - #region IndicesAndRanges_RangeIndexTypes - Index the = ^3; - Console.WriteLine(words[the]); - Range phrase = 1..4; - var text = words[phrase]; - foreach (var word in text) - Console.Write($"< {word} >"); - Console.WriteLine(); - #endregion - return 0; - } - - internal int Syntax_WhyChosenSemantics() - { - #region IndicesAndRanges_CreateRange - var numbers = Enumerable.Range(0, 100).ToArray(); - int x = 12; - int y = 25; - int z = 36; - #endregion - - #region IndicesAndRanges_MathWithLength - Console.WriteLine("===================== ^0 is the same as Length. =>"); - Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}"); - Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}"); - Console.WriteLine(); - #endregion - - #region IndicesAndRanges_Disjoint - Console.WriteLine("===================== Consecutive disjoint sequences. =>"); - Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:"); - Span x_y = numbers[x..y]; - Span y_z = numbers[y..z]; - Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through {y_z[^1]}"); - Console.WriteLine(); - #endregion - - #region IndicesAndRanges_RemoveFromEnds - Console.WriteLine("===================== Remove elements from both ends. =>"); - Console.WriteLine("numbers[x..^x] removes x elements at each end:"); - Span x_x = numbers[x..^x]; - Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}"); - Console.WriteLine(); - #endregion - - #region IndicesAndRanges_IncompleteRanges - Console.WriteLine("===================== Incomplete sequences imply 0, ^0 =>"); - Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]"); - Span start_x = numbers[..x]; - Span zero_x = numbers[0..x]; - Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}"); - Span z_end = numbers[z..]; - Span z_zero = numbers[z..]; - Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}"); - Console.WriteLine(); - #endregion - return 0; - } - - internal int ComputeMovingAverages() - { - #region IndicesAndRanges_MovingAverage - int[] sequence = Sequence(1000); - - - for(int start = 0; start < sequence.Length; start += 100) - { - Range r = start..start+10; - var (min, max, average) = MovingAverage(sequence, r); - Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}"); - } - - for (int start = 0; start < sequence.Length; start += 100) - { - Range r = ^(start + 10)..^start; - var (min, max, average) = MovingAverage(sequence, r); - Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}"); - } - - (int min, int max, double average) MovingAverage(int[] subSequence, Range range) => - ( - subSequence[range].Min(), - subSequence[range].Max(), - subSequence[range].Average() - ); - #endregion - return 0; - } - - - private int[] Sequence(int count) - { - return Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100)).ToArray(); - } - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/NullableReferences.cs b/Samples/csharp8/ExploreCsharpEight/NullableReferences.cs deleted file mode 100644 index 9e8f96ca3..000000000 --- a/Samples/csharp8/ExploreCsharpEight/NullableReferences.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ExploreCsharpEight -{ - #region Nullable_PersonDefinition - #nullable enable - internal class Person - { - public string FirstName { get; set; } - public string MiddleName { get; set; } - public string LastName { get; set; } - - public Person(string first, string last) => - (FirstName, LastName) = (first, last); - - public Person(string first, string middle, string last) => - (FirstName, MiddleName, LastName) = (first, middle, last); - - public override string ToString() => $"{FirstName} {MiddleName} {LastName}"; - } - #nullable restore - #endregion - - class NullableReferences - { - - #region Nullable_GetLengthMethod - #nullable enable - private static int GetLengthOfMiddleName(Person p) - { - string middleName = p.MiddleName; - return middleName.Length; - } - #nullable restore - #endregion - - internal int NullableTestBed() - { - #region Nullable_Usage - #nullable enable - Person miguel = new Person("Miguel", "de Icaza"); - var length = GetLengthOfMiddleName(miguel); - Console.WriteLine(length); - #nullable restore - //Was this tested on a person who doesn't have a middle name? - #endregion - return 0; - } - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/Patterns.cs b/Samples/csharp8/ExploreCsharpEight/Patterns.cs deleted file mode 100644 index e123890f8..000000000 --- a/Samples/csharp8/ExploreCsharpEight/Patterns.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using CommercialRegistration; -using ConsumerVehicleRegistration; -using LiveryRegistration; - - -namespace ExploreCsharpEight -{ - // I made this interface so I can usethe same test routine - // with each iteration of the toll calculator - interface ITollCalculator - { - decimal CalculateToll(object vehicle); - } - - class TollCalculator_V1 : ITollCalculator - { - #region Pattern_CalculateToll - public decimal CalculateToll(object vehicle) => - vehicle switch - { - Car c => 2.00m, - Taxi t => 3.50m, - Bus b => 5.00m, - DeliveryTruck t => 10.00m, - {} => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)), - null => throw new ArgumentNullException(nameof(vehicle)) - }; - #endregion - } - - class TollCalculator_V2 : ITollCalculator - { - #region Pattern_CarTaxiOccupancy - public decimal CalculateToll(object vehicle) => - vehicle switch - { - Car { Passengers: 0 } => 2.00m + 0.50m, - Car { Passengers: 1 } => 2.0m, - Car { Passengers: 2 } => 2.0m - 0.50m, - Car _ => 2.00m - 1.0m, - - Taxi { Fares: 0 } => 3.50m + 1.00m, - Taxi { Fares: 1 } => 3.50m, - Taxi { Fares: 2 } => 3.50m - 0.50m, - Taxi _ => 3.50m - 1.00m, - - Bus b => 5.00m, - DeliveryTruck t => 10.00m, - {} => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)), - null => throw new ArgumentNullException(nameof(vehicle)) - }; - #endregion - } - - class TollCalculator_V3 : ITollCalculator - { - #region Pattern_BusOccupancy - public decimal CalculateToll(object vehicle) => - vehicle switch - { - Car { Passengers: 0 } => 2.00m + 0.50m, - Car { Passengers: 1 } => 2.0m, - Car { Passengers: 2 } => 2.0m - 0.50m, - Car _ => 2.00m - 1.0m, - - Taxi { Fares: 0 } => 3.50m + 1.00m, - Taxi { Fares: 1 } => 3.50m, - Taxi { Fares: 2 } => 3.50m - 0.50m, - Taxi _ => 3.50m - 1.00m, - - Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m, - Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m, - Bus _ => 5.00m, - - DeliveryTruck t => 10.00m, - {} => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)), - null => throw new ArgumentNullException(nameof(vehicle)) - }; - #endregion - } - - class TollCalculator_V4 : ITollCalculator - { - #region Pattern_DeliveryTruckWeight - public decimal CalculateToll(object vehicle) => - vehicle switch - { - Car { Passengers: 0 } => 2.00m + 0.50m, - Car { Passengers: 1 } => 2.0m, - Car { Passengers: 2 } => 2.0m - 0.50m, - Car _ => 2.00m - 1.0m, - - Taxi { Fares: 0 } => 3.50m + 1.00m, - Taxi { Fares: 1 } => 3.50m, - Taxi { Fares: 2 } => 3.50m - 0.50m, - Taxi _ => 3.50m - 1.00m, - - Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m, - Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m, - Bus _ => 5.00m, - - DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m, - DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m, - DeliveryTruck _ => 10.00m, - - {} => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)), - null => throw new ArgumentNullException(nameof(vehicle)) - }; - #endregion - } - - class TollCalculator_V5 : ITollCalculator - { - #region Pattern_ChainedPatterns - public decimal CalculateToll(object vehicle) => - vehicle switch - { - Car c => c.Passengers switch - { - 0 => 2.00m + 0.5m, - 1 => 2.0m, - 2 => 2.0m - 0.5m, - _ => 2.00m - 1.0m - }, - - Taxi t => t.Fares switch - { - 0 => 3.50m + 1.00m, - 1 => 3.50m, - 2 => 3.50m - 0.50m, - _ => 3.50m - 1.00m - }, - - Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m, - Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m, - Bus b => 5.00m, - - DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m, - DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m, - DeliveryTruck t => 10.00m, - - {} => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)), - null => throw new ArgumentNullException(nameof(vehicle)) - }; - #endregion - - private enum TimeBand - { - MorningRush, - Daytime, - EveningRush, - Overnight - } - - private static TimeBand GetTimeBand(DateTime timeOfToll) - { - int hour = timeOfToll.Hour; - if (hour < 6) - return TimeBand.Overnight; - else if (hour < 10) - return TimeBand.MorningRush; - else if (hour < 16) - return TimeBand.Daytime; - else if (hour < 20) - return TimeBand.EveningRush; - else - return TimeBand.Overnight; - } - - private static bool IsWeekDay(DateTime timeOfToll) => - timeOfToll.DayOfWeek switch - { - DayOfWeek.Saturday => false, - DayOfWeek.Sunday => false, - _ => true - }; - - #region Pattern_PeakTime - public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) => - (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch - { - (true, TimeBand.Overnight, _) => 0.75m, - (true, TimeBand.Daytime, _) => 1.5m, - (true, TimeBand.MorningRush, true) => 2.0m, - (true, TimeBand.EveningRush, false) => 2.0m, - (_, _, _) => 1.0m, - }; - #endregion - - } - - class Patterns - { - internal int VehicleType() - { - #region Patterns_VehicleType - var tollCalc = new TollCalculator_V1(); - - var car = new Car(); - var taxi = new Taxi(); - var bus = new Bus(); - var truck = new DeliveryTruck(); - - Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}"); - Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}"); - Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}"); - Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}"); - - try - { - tollCalc.CalculateToll("this will fail"); - } - catch (ArgumentException e) - { - Console.WriteLine("Caught an argument exception when using the wrong type"); - } - try - { - tollCalc.CalculateToll(null); - } - catch (ArgumentNullException e) - { - Console.WriteLine("Caught an argument exception when using null"); - } - #endregion - return 0; - } - - internal int TestOccupancy(string regionName) - { - - ITollCalculator tollCalc = regionName switch - { - "Pattern_CarTaxiOccupancy" => new TollCalculator_V2() as ITollCalculator, - "Pattern_BusOccupancy" => new TollCalculator_V3() as ITollCalculator, - "Pattern_DeliveryTruckWeight" => new TollCalculator_V4() as ITollCalculator, - "Pattern_ChainedPatterns" => new TollCalculator_V5() as ITollCalculator, - _ => new TollCalculator_V1() as ITollCalculator, - }; - - var soloDriver = new Car(); - var twoRideShare = new Car { Passengers = 1 }; - var threeRideShare = new Car { Passengers = 2 }; - var fullVan = new Car { Passengers = 5 }; - var emptyTaxi = new Taxi(); - var singleFare = new Taxi { Fares = 1 }; - var doubleFare = new Taxi { Fares = 2 }; - var fullVanPool = new Taxi { Fares = 5 }; - var lowOccupantBus = new Bus { Capacity = 90, Riders = 15 }; - var normalBus = new Bus { Capacity = 90, Riders = 75 }; - var fullBus = new Bus { Capacity = 90, Riders = 85 }; - - var heavyTruck = new DeliveryTruck { GrossWeightClass = 7500 }; - var truck = new DeliveryTruck { GrossWeightClass = 4000 }; - var lightTruck = new DeliveryTruck { GrossWeightClass = 2500 }; - - Console.WriteLine($"The toll for a solo driver is {tollCalc.CalculateToll(soloDriver)}"); - Console.WriteLine($"The toll for a two ride share is {tollCalc.CalculateToll(twoRideShare)}"); - Console.WriteLine($"The toll for a three ride share is {tollCalc.CalculateToll(threeRideShare)}"); - Console.WriteLine($"The toll for a fullVan is {tollCalc.CalculateToll(fullVan)}"); - - Console.WriteLine($"The toll for an empty taxi is {tollCalc.CalculateToll(emptyTaxi)}"); - Console.WriteLine($"The toll for a single fare taxi is {tollCalc.CalculateToll(singleFare)}"); - Console.WriteLine($"The toll for a double fare taxi is {tollCalc.CalculateToll(doubleFare)}"); - Console.WriteLine($"The toll for a full van taxi is {tollCalc.CalculateToll(fullVanPool)}"); - - Console.WriteLine($"The toll for a low-occupant bus is {tollCalc.CalculateToll(lowOccupantBus)}"); - Console.WriteLine($"The toll for a regular bus is {tollCalc.CalculateToll(normalBus)}"); - Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(fullBus)}"); - - Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(heavyTruck)}"); - Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}"); - Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(lightTruck)}"); - - try - { - tollCalc.CalculateToll("this will fail"); - } - catch (ArgumentException e) - { - Console.WriteLine("Caught an argument exception when using the wrong type"); - } - try - { - tollCalc.CalculateToll(null!); - } - catch (ArgumentNullException e) - { - Console.WriteLine("Caught an argument exception when using null"); - } - return 1; - } - - internal int PeakPricing() - { - var tollCalc = new TollCalculator_V5(); - - var testTimes = new DateTime[] - { - new DateTime(2019, 3, 4, 8, 0, 0), // morning rush - new DateTime(2019, 3, 6, 11, 30, 0), // daytime - new DateTime(2019, 3, 7, 17, 15, 0), // evening rush - new DateTime(2019, 3, 14, 03, 30, 0), // overnight - - new DateTime(2019, 3, 16, 8, 30, 0), // weekend morning rush - new DateTime(2019, 3, 17, 14, 30, 0), // weekend daytime - new DateTime(2019, 3, 17, 18, 05, 0), // weekend evening rush - new DateTime(2019, 3, 16, 01, 30, 0), // weekend overnight - }; - - foreach (var time in testTimes) - { - Console.WriteLine($"Inbound premium at {time} is {tollCalc.PeakTimePremium(time, true)}"); - Console.WriteLine($"Outbound premium at {time} is {tollCalc.PeakTimePremium(time, false)}"); - } - return 0; - } - - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/Program.cs b/Samples/csharp8/ExploreCsharpEight/Program.cs deleted file mode 100644 index aff32bedb..000000000 --- a/Samples/csharp8/ExploreCsharpEight/Program.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace ExploreCsharpEight -{ - // These are my sample creation notes, with the goal - // of making a set of recommendations for upcoming tutorials (online or local) - - // Rule 1: Make a new class for each section. - // Rule 2: Each section should have two parts: - // a. Basic syntax for the new feature. - // b. A scenario for using the feature. - - // Questions: - // What is "package" used for? - // How to include non-editable blocks - class Program - { -#nullable enable - static async Task Main(string? region = null, - string? session = null, - string? package = null, - string? project = null, - string[]? args = null) - { - return region switch - { - "Pattern_CalculateToll" => new Patterns().VehicleType(), - "Pattern_CarTaxiOccupancy" => new Patterns().TestOccupancy("Pattern_CarTaxiOccupancy"), - "Pattern_BusOccupancy" => new Patterns().TestOccupancy("Pattern_BusOccupancy"), - "Pattern_DeliveryTruckWeight" => new Patterns().TestOccupancy("Pattern_DeliveryTruckWeight"), - "Pattern_ChainedPatterns" => new Patterns().TestOccupancy("Pattern_ChainedPatterns"), - "Pattern_PeakTime" => new Patterns().PeakPricing(), - - "LocalFunction_Counting" => new StaticLocalFunctions().LocalFunctionWithCapture(), - - "Using_Block" => new UsingDeclarationsRefStruct().OldStyle(), - "Using_Declaration" => new UsingDeclarationsRefStruct().NewStyle(), - - "Nullable_PersonDefinition" => new NullableReferences().NullableTestBed(), - "Nullable_GetLengthMethod" => new NullableReferences().NullableTestBed(), - "Nullable_Usage" => new NullableReferences().NullableTestBed(), - - "AsyncStreams_Declare" => await new AsyncStreams().ConsumeStream(), - "AsyncStreams_Consume" => await new AsyncStreams().ConsumeStream(), - - "IndicesAndRanges_LastIndex" => new IndicesAndRanges().Syntax_LastIndex(), - "IndicesAndRanges_Range" => new IndicesAndRanges().Syntax_Range(), - "IndicesAndRanges_LastRange" => new IndicesAndRanges().Syntax_LastRange(), - "IndicesAndRanges_PartialRanges" => new IndicesAndRanges().Syntax_PartialRange(), - "IndicesAndRanges_RangeIndexTypes" => new IndicesAndRanges().Syntax_IndexRangeType(), - "IndicesAndRanges_CreateRange" => new IndicesAndRanges().Syntax_WhyChosenSemantics(), - "IndicesAndRanges_MathWithLength" => new IndicesAndRanges().Syntax_WhyChosenSemantics(), - "IndicesAndRanges_Disjoint" => new IndicesAndRanges().Syntax_WhyChosenSemantics(), - "IndicesAndRanges_RemoveFromEnds" => new IndicesAndRanges().Syntax_WhyChosenSemantics(), - "IndicesAndRanges_IncompleteRanges" => new IndicesAndRanges().ComputeMovingAverages(), - "IndicesAndRanges_MovingAverage" => new IndicesAndRanges().ComputeMovingAverages(), - null => await RunAll(), - _ => await RunAll() - }; - } -#nullable restore - - private static async Task RunAll() - { - Console.WriteLine("========== Starting pattern matching samples. =========="); - var patterns = new Patterns(); - Console.WriteLine(" ========== Run vehicle type. =========="); - patterns.VehicleType(); - Console.WriteLine(" ========== Run car taxi occupancy. =========="); - patterns.TestOccupancy("Pattern_CarTaxiOccupancy"); - Console.WriteLine(" ========== Run bus occupancy. =========="); - patterns.TestOccupancy("Pattern_BusOccupancy"); - Console.WriteLine(" ========== Run delivery truck. =========="); - patterns.TestOccupancy("Pattern_DeliveryTruckWeight"); - Console.WriteLine(" ========== Run chained patterns. =========="); - patterns.TestOccupancy("Pattern_ChainedPatterns"); - Console.WriteLine(" ========== Run peak pricing. =========="); - patterns.PeakPricing(); - - Console.WriteLine("========== Starting static local functions. =========="); - var localFuncs = new StaticLocalFunctions(); - Console.WriteLine(" ========== Run local func capture. =========="); - localFuncs.LocalFunctionWithCapture(); - Console.WriteLine(" ========== Run local static func. =========="); - localFuncs.LocalFunctionWithNoCapture(); - - Console.WriteLine("========== Starting using declaration / ref struct. =========="); - var usings = new UsingDeclarationsRefStruct(); - Console.WriteLine(" ========== Run using block style. =========="); - usings.OldStyle(); - Console.WriteLine(" ========== Run declaration style. =========="); - usings.NewStyle(); - - Console.WriteLine("========== Starting nullable reference samples. =========="); - var nullableSamples = new NullableReferences(); - Console.WriteLine(" ========== Run nullable. =========="); - try - { - nullableSamples.NullableTestBed(); - }catch (NullReferenceException) - { - Console.WriteLine("Initial nullable sample threw the expected NullReferenceException"); - } - Console.WriteLine("========== Starting Async Stream Samples. =========="); - var asyncSamples = new AsyncStreams(); - Console.WriteLine(" ========== Generate sequence. =========="); - await asyncSamples.ConsumeStream(); - - Console.WriteLine("========== Starting Index and Range Samples. =========="); - var indexSamples = new IndicesAndRanges(); - Console.WriteLine(" ========== Last Index. =========="); - indexSamples.Syntax_LastIndex(); - Console.WriteLine(" ========== Range. =========="); - indexSamples.Syntax_Range(); - Console.WriteLine(" ========== Last Range. =========="); - indexSamples.Syntax_LastRange(); - Console.WriteLine(" ========== Partial Range. =========="); - indexSamples.Syntax_PartialRange(); - Console.WriteLine(" ========== Index and Range types. =========="); - indexSamples.Syntax_IndexRangeType(); - Console.WriteLine(" ========== Why this syntax. =========="); - indexSamples.Syntax_WhyChosenSemantics(); - Console.WriteLine(" ========== Scenario. =========="); - indexSamples.ComputeMovingAverages(); - return 0; - } - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/StaticLocalFunctions.cs b/Samples/csharp8/ExploreCsharpEight/StaticLocalFunctions.cs deleted file mode 100644 index 9405a935e..000000000 --- a/Samples/csharp8/ExploreCsharpEight/StaticLocalFunctions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace ExploreCsharpEight -{ - class StaticLocalFunctions - { - #region LocalFunction_Counting - public IEnumerable Counter(int start, int end) - { - if (start >= end) throw new ArgumentOutOfRangeException("start must be less than end"); - - return localCounter(); - - IEnumerable localCounter() - { - for (int i = start; i < end; i++) - yield return i; - } - } - #endregion - - internal int LocalFunctionWithCapture() - { - var items = Counter(1, 10); - foreach(var item in items) - { - Console.WriteLine(item); - } - return 1; - } - - internal int LocalFunctionWithNoCapture() - { - var items = StaticCounter(1, 10); - foreach (var item in items) - { - Console.WriteLine(item); - } - return 1; - } - - - #region LocalFunction_Static - public IEnumerable StaticCounter(int start, int end) - { - if (start >= end) throw new ArgumentOutOfRangeException("start must be less than end"); - - return localCounter(start, end); - - static IEnumerable localCounter(int first, int endLocation) - { - for (int i = first; i < endLocation; i++) - yield return i; - } - } - #endregion - - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/UsingDeclarationsRefStruct.cs b/Samples/csharp8/ExploreCsharpEight/UsingDeclarationsRefStruct.cs deleted file mode 100644 index 6598ac7e3..000000000 --- a/Samples/csharp8/ExploreCsharpEight/UsingDeclarationsRefStruct.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace ExploreCsharpEight -{ - internal class ResourceHog : IDisposable - { - private string name; - private bool beenDisposed; - - public ResourceHog(string name) => this.name = name; - - public void Dispose() - { - beenDisposed = true; - Console.WriteLine($"Disposing {name}"); - } - - internal void CopyFrom(ResourceHog src) - { - switch (beenDisposed, src.beenDisposed) - { - case (true, true): throw new ObjectDisposedException($"Resource {name} has already been disposed"); - case (true, false): throw new ObjectDisposedException($"Resource {name} has already been disposed"); - case (false, true): throw new ObjectDisposedException($"Resource {name} has already been disposed"); - default: Console.WriteLine($"Copying from {src.name} to {name}"); return; - }; - - } - } - - internal class UsingDeclarationsRefStruct - { - internal int OldStyle() - { - #region Using_Block - using (var src = new ResourceHog("source")) - { - using (var dest = new ResourceHog("destination")) - { - dest.CopyFrom(src); - } - Console.WriteLine("After closing destination block"); - } - Console.WriteLine("After closing source block"); - #endregion - return 0; - } - internal int NewStyle() - { - #region Using_Declaration - using var src = new ResourceHog("source"); - using var dest = new ResourceHog("destination"); - dest.CopyFrom(src); - Console.WriteLine("Exiting block"); - #endregion - return 0; - } - } -} diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.deps.json b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.deps.json deleted file mode 100644 index 0b25de03e..000000000 --- a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.deps.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v3.0", - "signature": "83d3cd37118cab640e152a081f63db089e152d37" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v3.0": { - "ExploreCsharpEight/1.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0-preview3.19128.7", - "Microsoft.WindowsDesktop.App": "3.0.0-preview3-27504-2", - "System.CommandLine.DragonFruit": "0.2.0-alpha.19174.3", - "System.CommandLine.Experimental": "0.2.0-alpha.19174.3", - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": "3.0.0-preview3-27503-5" - }, - "runtime": { - "ExploreCsharpEight.dll": {} - } - }, - "Microsoft.CSharp/4.4.1": {}, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": {}, - "Microsoft.NETCore.Targets/2.0.0": {}, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "dependencies": { - "Microsoft.NETCore.Platforms": "3.0.0-preview3.19128.7", - "Microsoft.NETCore.Targets": "2.0.0" - } - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": {}, - "System.CommandLine.DragonFruit/0.2.0-alpha.19174.3": { - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19174.3", - "System.CommandLine.Rendering": "0.2.0-alpha.19174.3" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17403" - } - } - }, - "System.CommandLine.Experimental/0.2.0-alpha.19174.3": { - "dependencies": { - "Microsoft.CSharp": "4.4.1" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17403" - } - } - }, - "System.CommandLine.Rendering/0.2.0-alpha.19174.3": { - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19174.3" - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": { - "assemblyVersion": "0.2.0.0", - "fileVersion": "0.2.19.17403" - } - } - } - } - }, - "libraries": { - "ExploreCsharpEight/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.CSharp/4.4.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-A5hI3gk6WpcBI0QGZY6/d5CCaYUxJgi7iENn1uYEng+Olo8RfI5ReGVkjXjeu3VR3srLvVYREATXa2M0X7FYJA==", - "path": "microsoft.csharp/4.4.1", - "hashPath": "microsoft.csharp.4.4.1.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "type": "package", - "serviceable": true, - "sha512": "sha512-4/k52fBHvM1RNPoBMik0Y3wIovsUGaTadNGH75iLGjQCJPRq2UBivEkhYrYkIFnu+K9kTYJcmXCDF6SFzMBrqA==", - "path": "microsoft.netcore.platforms/3.0.0-preview3.19128.7", - "hashPath": "microsoft.netcore.platforms.3.0.0-preview3.19128.7.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-odP/tJj1z6GylFpNo7pMtbd/xQgTC3Ex2If63dRTL38bBNMwsBnJ+RceUIyHdRBC0oik/3NehYT+oECwBhIM3Q==", - "path": "microsoft.netcore.targets/2.0.0", - "hashPath": "microsoft.netcore.targets.2.0.0.nupkg.sha512" - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t7Iw6nkx03aOXgZ7L2ZJLN7hGY7FlUaxIyH6UbGhjHCJABxfV8Bx3xOe6E63xa1HVeoccCAdNPctTX2s2zj5SQ==", - "path": "microsoft.windowsdesktop.app/3.0.0-preview3-27504-2", - "hashPath": "microsoft.windowsdesktop.app.3.0.0-preview3-27504-2.nupkg.sha512" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9xq1NTCrWg3A4/6K/COActt25NJaw1a6bx0CTIkgbYv2TkpveKXZCCSFyDnT5Ff9nBvZkBq4Y4r/tZNIU5T8pg==", - "path": "runtime.win-x64.microsoft.netcore.dotnetapphost/3.0.0-preview3-27503-5", - "hashPath": "runtime.win-x64.microsoft.netcore.dotnetapphost.3.0.0-preview3-27503-5.nupkg.sha512" - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19174.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YtwUWDTkIaj9bUnF7/xifxZ04gp6XKF27Y8EmZpwjMpDeSh+V9uOVjtC1O3BAwQlst4Nr5d3Ytn02aWPOu82YQ==", - "path": "system.commandline.dragonfruit/0.2.0-alpha.19174.3", - "hashPath": "system.commandline.dragonfruit.0.2.0-alpha.19174.3.nupkg.sha512" - }, - "System.CommandLine.Experimental/0.2.0-alpha.19174.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PTJCVcj0rkIYPZzKZbU4uLdEvosmnX2CQR98rY6+efIn96zRtbljl74sPFRFZlLxeLprt42FeZNsYH+QdxCHPA==", - "path": "system.commandline.experimental/0.2.0-alpha.19174.3", - "hashPath": "system.commandline.experimental.0.2.0-alpha.19174.3.nupkg.sha512" - }, - "System.CommandLine.Rendering/0.2.0-alpha.19174.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TbLA9yUwzdd/DRlyWS53x/NlOkJ0k1WI+YE8Ne2w/Wk+H9w3XqzRBVZBoL7b2JpHvW2s0mNi4tQRCCi5qgmxkQ==", - "path": "system.commandline.rendering/0.2.0-alpha.19174.3", - "hashPath": "system.commandline.rendering.0.2.0-alpha.19174.3.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.dll b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.dll deleted file mode 100644 index 63f140bd2..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.dll and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.exe b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.exe deleted file mode 100644 index 882972222..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.exe and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.pdb b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.pdb deleted file mode 100644 index 1fc383254..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.pdb and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.dev.json b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.dev.json deleted file mode 100644 index 22852d52d..000000000 --- a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "additionalProbingPaths": [ - "C:\\Users\\Maria Naggaga\\.dotnet\\store\\|arch|\\|tfm|", - "C:\\Users\\Maria Naggaga\\.nuget\\packages", - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ] - } -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.json b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.json deleted file mode 100644 index b6f483737..000000000 --- a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp3.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "3.0.0-preview3-27503-5" - } - } -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.xml b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.xml deleted file mode 100644 index fadd615d2..000000000 --- a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/ExploreCsharpEight.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - ExploreCsharpEight - - - - diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll deleted file mode 100644 index fcf94410a..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.DragonFruit.dll and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll deleted file mode 100644 index 827847321..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.Rendering.dll and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.dll b/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.dll deleted file mode 100644 index 17bcbfffb..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/bin/Debug/netcoreapp3.0/System.CommandLine.dll and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfo.cs b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfo.cs deleted file mode 100644 index ae3a7de4c..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Generated by the MSBuild WriteCodeFragment class. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("ExploreCsharpEight")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] -[assembly: System.Reflection.AssemblyProductAttribute("ExploreCsharpEight")] -[assembly: System.Reflection.AssemblyTitleAttribute("ExploreCsharpEight")] -[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfoInputs.cache b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfoInputs.cache deleted file mode 100644 index 550a9becc..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -5a680251f06d538a06f97b6fb4d5a77bc9801838 diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.Program.g.cs b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.Program.g.cs deleted file mode 100644 index e5263d9af..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.Program.g.cs and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.assets.cache b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.assets.cache deleted file mode 100644 index 39e89d356..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.assets.cache and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.CopyComplete b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.CopyComplete deleted file mode 100644 index e69de29bb..000000000 diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.CoreCompileInputs.cache b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.CoreCompileInputs.cache deleted file mode 100644 index c9f33aff1..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.CoreCompileInputs.cache +++ /dev/null @@ -1 +0,0 @@ -87c6cf583c491a3bc2067073f597c7c1f4e7073d diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.FileListAbsolute.txt b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.FileListAbsolute.txt deleted file mode 100644 index 460683466..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csproj.FileListAbsolute.txt +++ /dev/null @@ -1,36 +0,0 @@ -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.exe -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.deps.json -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.runtimeconfig.json -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.runtimeconfig.dev.json -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csproj.CoreCompileInputs.cache -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.AssemblyInfoInputs.cache -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.AssemblyInfo.cs -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.dll -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.pdb -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.xml -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.DragonFruit.dll -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.dll -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.Rendering.dll -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csprojAssemblyReference.cache -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csproj.CopyComplete -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.dll -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.xml -C:\dev\tdn_samples\tdnsamples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.pdb -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.exe -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.deps.json -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.runtimeconfig.json -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.runtimeconfig.dev.json -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.dll -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.pdb -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\ExploreCsharpEight.xml -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.DragonFruit.dll -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.dll -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\bin\Debug\netcoreapp3.0\System.CommandLine.Rendering.dll -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csprojAssemblyReference.cache -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csproj.CoreCompileInputs.cache -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.AssemblyInfoInputs.cache -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.AssemblyInfo.cs -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.csproj.CopyComplete -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.dll -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.xml -C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\Debug\netcoreapp3.0\ExploreCsharpEight.pdb diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csprojAssemblyReference.cache b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csprojAssemblyReference.cache deleted file mode 100644 index c7dd66424..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.csprojAssemblyReference.cache and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.dll b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.dll deleted file mode 100644 index 63f140bd2..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.dll and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.exe b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.exe deleted file mode 100644 index 882972222..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.exe and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.pdb b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.pdb deleted file mode 100644 index 1fc383254..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.pdb and /dev/null differ diff --git a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.xml b/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.xml deleted file mode 100644 index fadd615d2..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/Debug/netcoreapp3.0/ExploreCsharpEight.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - ExploreCsharpEight - - - - diff --git a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.cache b/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.cache deleted file mode 100644 index 568e35bdb..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.cache +++ /dev/null @@ -1,5 +0,0 @@ -{ - "version": 1, - "dgSpecHash": "yo2QA/jKo5fg23XBzYimVEwnYWykRDW/zAYK9o/ZtuUqCYkeCah8MNgTVkI54DJ2Kkiq+cdisZspiBNCKePYxw==", - "success": true -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.dgspec.json b/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.dgspec.json deleted file mode 100644 index a7ed4e347..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.dgspec.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "format": 1, - "restore": { - "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj": {} - }, - "projects": { - "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj", - "projectName": "ExploreCsharpEight", - "projectPath": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj", - "packagesPath": "C:\\Users\\Maria Naggaga\\.nuget\\packages\\", - "outputPath": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ], - "configFilePaths": [ - "C:\\dev\\NuGet.Config", - "C:\\Users\\Maria Naggaga\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "netcoreapp3.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json": {}, - "https://msazure.pkgs.visualstudio.com/_packaging/MLS/nuget/v3/index.json": {} - }, - "frameworks": { - "netcoreapp3.0": { - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - } - }, - "frameworks": { - "netcoreapp3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": { - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3.19128.7, )", - "autoReferenced": true - }, - "Microsoft.WindowsDesktop.App": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27504-2, )", - "autoReferenced": true - }, - "System.CommandLine.DragonFruit": { - "target": "Package", - "version": "[0.2.0-alpha.19174.3, )" - }, - "System.CommandLine.Experimental": { - "target": "Package", - "version": "[0.2.0-alpha.19174.3, )" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27503-5, )", - "autoReferenced": true - } - }, - "imports": [ - "net461" - ], - "assetTargetFallback": true, - "warn": true - } - } - } - } -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.props b/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.props deleted file mode 100644 index e9430b1a7..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.props +++ /dev/null @@ -1,18 +0,0 @@ - - - - True - NuGet - C:\dev\try\try\Samples\csharp8\ExploreCsharpEight\obj\project.assets.json - $(UserProfile)\.nuget\packages\ - C:\Users\Maria Naggaga\.nuget\packages\;C:\Program Files\dotnet\sdk\NuGetFallbackFolder - PackageReference - 5.0.0 - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.targets b/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.targets deleted file mode 100644 index 32692635f..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/ExploreCsharpEight.csproj.nuget.g.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/obj/project.assets.json b/Samples/csharp8/ExploreCsharpEight/obj/project.assets.json deleted file mode 100644 index 59a7046e8..000000000 --- a/Samples/csharp8/ExploreCsharpEight/obj/project.assets.json +++ /dev/null @@ -1,412 +0,0 @@ -{ - "version": 3, - "targets": { - ".NETCoreApp,Version=v3.0": { - "Microsoft.CSharp/4.4.1": { - "type": "package", - "compile": { - "ref/netcoreapp2.0/_._": {} - }, - "runtime": { - "lib/netcoreapp2.0/_._": {} - } - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "type": "package", - "compile": { - "lib/netstandard1.0/_._": {} - }, - "runtime": { - "lib/netstandard1.0/_._": {} - } - }, - "Microsoft.NETCore.Targets/2.0.0": { - "type": "package", - "compile": { - "lib/netstandard1.0/_._": {} - }, - "runtime": { - "lib/netstandard1.0/_._": {} - } - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "type": "package", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.0.0", - "Microsoft.NETCore.Targets": "2.0.0" - }, - "compile": { - "ref/netcoreapp3.0/_._": {} - }, - "build": { - "build/netcoreapp3.0/_._": {} - } - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "type": "package", - "runtimeTargets": { - "runtimes/win-x64/native/_._": { - "assetType": "native", - "rid": "win-x64" - } - } - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19174.3": { - "type": "package", - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19174.3", - "System.CommandLine.Rendering": "0.2.0-alpha.19174.3" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll": {} - }, - "build": { - "build/netstandard2.0/System.CommandLine.DragonFruit.props": {}, - "build/netstandard2.0/System.CommandLine.DragonFruit.targets": {} - } - }, - "System.CommandLine.Experimental/0.2.0-alpha.19174.3": { - "type": "package", - "dependencies": { - "Microsoft.CSharp": "4.4.1" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.dll": {} - } - }, - "System.CommandLine.Rendering/0.2.0-alpha.19174.3": { - "type": "package", - "dependencies": { - "System.CommandLine.Experimental": "0.2.0-alpha.19174.3" - }, - "compile": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": {} - }, - "runtime": { - "lib/netstandard2.0/System.CommandLine.Rendering.dll": {} - } - } - } - }, - "libraries": { - "Microsoft.CSharp/4.4.1": { - "sha512": "A5hI3gk6WpcBI0QGZY6/d5CCaYUxJgi7iENn1uYEng+Olo8RfI5ReGVkjXjeu3VR3srLvVYREATXa2M0X7FYJA==", - "type": "package", - "path": "microsoft.csharp/4.4.1", - "files": [ - ".nupkg.metadata", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/MonoAndroid10/_._", - "lib/MonoTouch10/_._", - "lib/net45/_._", - "lib/netcore50/Microsoft.CSharp.dll", - "lib/netcoreapp2.0/_._", - "lib/netstandard1.3/Microsoft.CSharp.dll", - "lib/netstandard2.0/Microsoft.CSharp.dll", - "lib/portable-net45+win8+wp8+wpa81/_._", - "lib/win8/_._", - "lib/wp80/_._", - "lib/wpa81/_._", - "lib/xamarinios10/_._", - "lib/xamarinmac20/_._", - "lib/xamarintvos10/_._", - "lib/xamarinwatchos10/_._", - "microsoft.csharp.4.4.1.nupkg.sha512", - "microsoft.csharp.nuspec", - "ref/MonoAndroid10/_._", - "ref/MonoTouch10/_._", - "ref/net45/_._", - "ref/netcore50/Microsoft.CSharp.dll", - "ref/netcore50/Microsoft.CSharp.xml", - "ref/netcore50/de/Microsoft.CSharp.xml", - "ref/netcore50/es/Microsoft.CSharp.xml", - "ref/netcore50/fr/Microsoft.CSharp.xml", - "ref/netcore50/it/Microsoft.CSharp.xml", - "ref/netcore50/ja/Microsoft.CSharp.xml", - "ref/netcore50/ko/Microsoft.CSharp.xml", - "ref/netcore50/ru/Microsoft.CSharp.xml", - "ref/netcore50/zh-hans/Microsoft.CSharp.xml", - "ref/netcore50/zh-hant/Microsoft.CSharp.xml", - "ref/netcoreapp2.0/_._", - "ref/netstandard1.0/Microsoft.CSharp.dll", - "ref/netstandard1.0/Microsoft.CSharp.xml", - "ref/netstandard1.0/de/Microsoft.CSharp.xml", - "ref/netstandard1.0/es/Microsoft.CSharp.xml", - "ref/netstandard1.0/fr/Microsoft.CSharp.xml", - "ref/netstandard1.0/it/Microsoft.CSharp.xml", - "ref/netstandard1.0/ja/Microsoft.CSharp.xml", - "ref/netstandard1.0/ko/Microsoft.CSharp.xml", - "ref/netstandard1.0/ru/Microsoft.CSharp.xml", - "ref/netstandard1.0/zh-hans/Microsoft.CSharp.xml", - "ref/netstandard1.0/zh-hant/Microsoft.CSharp.xml", - "ref/netstandard2.0/Microsoft.CSharp.dll", - "ref/netstandard2.0/Microsoft.CSharp.xml", - "ref/portable-net45+win8+wp8+wpa81/_._", - "ref/win8/_._", - "ref/wp80/_._", - "ref/wpa81/_._", - "ref/xamarinios10/_._", - "ref/xamarinmac20/_._", - "ref/xamarintvos10/_._", - "ref/xamarinwatchos10/_._", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.NETCore.Platforms/3.0.0-preview3.19128.7": { - "sha512": "4/k52fBHvM1RNPoBMik0Y3wIovsUGaTadNGH75iLGjQCJPRq2UBivEkhYrYkIFnu+K9kTYJcmXCDF6SFzMBrqA==", - "type": "package", - "path": "microsoft.netcore.platforms/3.0.0-preview3.19128.7", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/netstandard1.0/_._", - "microsoft.netcore.platforms.3.0.0-preview3.19128.7.nupkg.sha512", - "microsoft.netcore.platforms.nuspec", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.NETCore.Targets/2.0.0": { - "sha512": "odP/tJj1z6GylFpNo7pMtbd/xQgTC3Ex2If63dRTL38bBNMwsBnJ+RceUIyHdRBC0oik/3NehYT+oECwBhIM3Q==", - "type": "package", - "path": "microsoft.netcore.targets/2.0.0", - "files": [ - ".nupkg.metadata", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "lib/netstandard1.0/_._", - "microsoft.netcore.targets.2.0.0.nupkg.sha512", - "microsoft.netcore.targets.nuspec", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "Microsoft.WindowsDesktop.App/3.0.0-preview3-27504-2": { - "sha512": "t7Iw6nkx03aOXgZ7L2ZJLN7hGY7FlUaxIyH6UbGhjHCJABxfV8Bx3xOe6E63xa1HVeoccCAdNPctTX2s2zj5SQ==", - "type": "package", - "path": "microsoft.windowsdesktop.app/3.0.0-preview3-27504-2", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "build/netcoreapp3.0/Microsoft.WindowsDesktop.App.targets", - "build/netcoreapp3.0/ref-mscorlib/System.ComponentModel.dll", - "build/netcoreapp3.0/ref-mscorlib/System.Runtime.CompilerServices.VisualC.dll", - "build/netcoreapp3.0/ref-mscorlib/System.Runtime.dll", - "build/netcoreapp3.0/ref-mscorlib/mscorlib.dll", - "microsoft.windowsdesktop.app.3.0.0-preview3-27504-2.nupkg.sha512", - "microsoft.windowsdesktop.app.nuspec", - "ref/netcoreapp3.0/Accessibility.dll", - "ref/netcoreapp3.0/Microsoft.Win32.Registry.dll", - "ref/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll", - "ref/netcoreapp3.0/PresentationCore-CommonResources.dll", - "ref/netcoreapp3.0/PresentationCore.dll", - "ref/netcoreapp3.0/PresentationFramework.Aero.dll", - "ref/netcoreapp3.0/PresentationFramework.Aero2.dll", - "ref/netcoreapp3.0/PresentationFramework.AeroLite.dll", - "ref/netcoreapp3.0/PresentationFramework.Classic.dll", - "ref/netcoreapp3.0/PresentationFramework.Luna.dll", - "ref/netcoreapp3.0/PresentationFramework.Royale.dll", - "ref/netcoreapp3.0/PresentationFramework.dll", - "ref/netcoreapp3.0/PresentationUI.dll", - "ref/netcoreapp3.0/ReachFramework.dll", - "ref/netcoreapp3.0/System.CodeDom.dll", - "ref/netcoreapp3.0/System.Configuration.ConfigurationManager.dll", - "ref/netcoreapp3.0/System.Design.dll", - "ref/netcoreapp3.0/System.Drawing.Common.dll", - "ref/netcoreapp3.0/System.Drawing.Design.dll", - "ref/netcoreapp3.0/System.Drawing.dll", - "ref/netcoreapp3.0/System.Printing.dll", - "ref/netcoreapp3.0/System.Security.AccessControl.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Cng.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Pkcs.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.ProtectedData.dll", - "ref/netcoreapp3.0/System.Security.Cryptography.Xml.dll", - "ref/netcoreapp3.0/System.Security.Permissions.dll", - "ref/netcoreapp3.0/System.Security.Principal.Windows.dll", - "ref/netcoreapp3.0/System.Windows.Controls.Ribbon.dll", - "ref/netcoreapp3.0/System.Windows.Extensions.dll", - "ref/netcoreapp3.0/System.Windows.Forms.Design.Editors.dll", - "ref/netcoreapp3.0/System.Windows.Forms.Design.dll", - "ref/netcoreapp3.0/System.Windows.Forms.dll", - "ref/netcoreapp3.0/System.Windows.Input.Manipulations.dll", - "ref/netcoreapp3.0/System.Windows.Presentation.dll", - "ref/netcoreapp3.0/System.Xaml.dll", - "ref/netcoreapp3.0/UIAutomationClient.dll", - "ref/netcoreapp3.0/UIAutomationClientSideProviders.dll", - "ref/netcoreapp3.0/UIAutomationProvider.dll", - "ref/netcoreapp3.0/UIAutomationTypes.dll", - "ref/netcoreapp3.0/WindowsBase.dll", - "ref/netcoreapp3.0/WindowsFormsIntegration.dll", - "runtime.json", - "useSharedDesignerContext.txt", - "version.txt" - ] - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost/3.0.0-preview3-27503-5": { - "sha512": "9xq1NTCrWg3A4/6K/COActt25NJaw1a6bx0CTIkgbYv2TkpveKXZCCSFyDnT5Ff9nBvZkBq4Y4r/tZNIU5T8pg==", - "type": "package", - "path": "runtime.win-x64.microsoft.netcore.dotnetapphost/3.0.0-preview3-27503-5", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - "runtime.win-x64.microsoft.netcore.dotnetapphost.3.0.0-preview3-27503-5.nupkg.sha512", - "runtime.win-x64.microsoft.netcore.dotnetapphost.nuspec", - "runtimes/win-x64/native/apphost.exe", - "runtimes/win-x64/native/comhost.dll", - "version.txt" - ] - }, - "System.CommandLine.DragonFruit/0.2.0-alpha.19174.3": { - "sha512": "YtwUWDTkIaj9bUnF7/xifxZ04gp6XKF27Y8EmZpwjMpDeSh+V9uOVjtC1O3BAwQlst4Nr5d3Ytn02aWPOu82YQ==", - "type": "package", - "path": "system.commandline.dragonfruit/0.2.0-alpha.19174.3", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "build/netstandard2.0/System.CommandLine.DragonFruit.props", - "build/netstandard2.0/System.CommandLine.DragonFruit.targets", - "lib/netstandard2.0/System.CommandLine.DragonFruit.dll", - "lib/netstandard2.0/System.CommandLine.DragonFruit.pdb", - "system.commandline.dragonfruit.0.2.0-alpha.19174.3.nupkg.sha512", - "system.commandline.dragonfruit.nuspec" - ] - }, - "System.CommandLine.Experimental/0.2.0-alpha.19174.3": { - "sha512": "PTJCVcj0rkIYPZzKZbU4uLdEvosmnX2CQR98rY6+efIn96zRtbljl74sPFRFZlLxeLprt42FeZNsYH+QdxCHPA==", - "type": "package", - "path": "system.commandline.experimental/0.2.0-alpha.19174.3", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/netstandard2.0/System.CommandLine.dll", - "lib/netstandard2.0/System.CommandLine.pdb", - "system.commandline.experimental.0.2.0-alpha.19174.3.nupkg.sha512", - "system.commandline.experimental.nuspec" - ] - }, - "System.CommandLine.Rendering/0.2.0-alpha.19174.3": { - "sha512": "TbLA9yUwzdd/DRlyWS53x/NlOkJ0k1WI+YE8Ne2w/Wk+H9w3XqzRBVZBoL7b2JpHvW2s0mNi4tQRCCi5qgmxkQ==", - "type": "package", - "path": "system.commandline.rendering/0.2.0-alpha.19174.3", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/netstandard2.0/System.CommandLine.Rendering.dll", - "lib/netstandard2.0/System.CommandLine.Rendering.pdb", - "system.commandline.rendering.0.2.0-alpha.19174.3.nupkg.sha512", - "system.commandline.rendering.nuspec" - ] - } - }, - "projectFileDependencyGroups": { - ".NETCoreApp,Version=v3.0": [ - "Microsoft.NETCore.Platforms >= 3.0.0-preview3.19128.7", - "Microsoft.WindowsDesktop.App >= 3.0.0-preview3-27504-2", - "System.CommandLine.DragonFruit >= 0.2.0-alpha.19174.3", - "System.CommandLine.Experimental >= 0.2.0-alpha.19174.3", - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost >= 3.0.0-preview3-27503-5" - ] - }, - "packageFolders": { - "C:\\Users\\Maria Naggaga\\.nuget\\packages\\": {}, - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder": {} - }, - "project": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj", - "projectName": "ExploreCsharpEight", - "projectPath": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\ExploreCsharpEight.csproj", - "packagesPath": "C:\\Users\\Maria Naggaga\\.nuget\\packages\\", - "outputPath": "C:\\dev\\try\\try\\Samples\\csharp8\\ExploreCsharpEight\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ], - "configFilePaths": [ - "C:\\dev\\NuGet.Config", - "C:\\Users\\Maria Naggaga\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "netcoreapp3.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json": {}, - "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json": {}, - "https://msazure.pkgs.visualstudio.com/_packaging/MLS/nuget/v3/index.json": {} - }, - "frameworks": { - "netcoreapp3.0": { - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - } - }, - "frameworks": { - "netcoreapp3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": { - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3.19128.7, )", - "autoReferenced": true - }, - "Microsoft.WindowsDesktop.App": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27504-2, )", - "autoReferenced": true - }, - "System.CommandLine.DragonFruit": { - "target": "Package", - "version": "[0.2.0-alpha.19174.3, )" - }, - "System.CommandLine.Experimental": { - "target": "Package", - "version": "[0.2.0-alpha.19174.3, )" - }, - "runtime.win-x64.Microsoft.NETCore.DotNetAppHost": { - "include": "None", - "suppressParent": "All", - "target": "Package", - "version": "[3.0.0-preview3-27503-5, )", - "autoReferenced": true - } - }, - "imports": [ - "net461" - ], - "assetTargetFallback": true, - "warn": true - } - } - } -} \ No newline at end of file diff --git a/Samples/csharp8/ExploreCsharpEight/package_fullBuild.binlog b/Samples/csharp8/ExploreCsharpEight/package_fullBuild.binlog deleted file mode 100644 index 0d59c7673..000000000 Binary files a/Samples/csharp8/ExploreCsharpEight/package_fullBuild.binlog and /dev/null differ diff --git a/Samples/csharp8/asynchronous-streams.md b/Samples/csharp8/asynchronous-streams.md deleted file mode 100644 index b591a3b62..000000000 --- a/Samples/csharp8/asynchronous-streams.md +++ /dev/null @@ -1,24 +0,0 @@ -## Asynchronous streams - -Starting with C# 8.0, you can create and consume streams asynchronously. A method that returns an asynchronous stream has three properties: - -1. It's declared with the `async` modifier. -1. It returns an `System.Collections.Generic.IAsyncEnumerable` -1. The method contains `yield return` statements to return successive elements in the asynchronous stream. - -Consuming an asynchronous stream requires you to add the `await` keyword before the `foreach` keyword when you enumerate the elements of the stream. Adding the `await` keyword requires the method that enumerates the asynchronous stream to be declared with the `async` modifier and to return a type allowed for an `async` method. Typically that means returning a `System.Threading.Tasks.Task` or `System.Threading.Tasks.Task`. It can also be a `System.Threading.Tasks.ValueTask` or `System.Threading.Tasks.ValueTask`. A method can both consume and produce an asynchronous stream, which means it would return an `System.Collections.Generic.IAsyncEnumerable`. The following code generates a sequence from 0 to 19. Every 3 elements, it pauses for 2 seconds to simulate retrieving the next set from a device: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/AsyncStreams.cs --region AsyncStreams_Declare --session async-stream -``` - -You would enumerate the sequence using the `await foreach` statement: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/AsyncStreams.cs --region AsyncStreams_Consume --session async-stream -``` - -Click the run button to experiment. Look at the times when elements are retrieved. Change some of the constants to experiment with different values, or time delays. - -> *Note*: -> This sandbox environment will timeout and halt programs that appear to be stuck. Avoid very long delays or very large sequences. - -#### Next: [Indices and ranges »](./indices-and-ranges.md) Previous: [Nullable reference types «](./nullable-fix-class.md) Home: [Home](index.md) diff --git a/Samples/csharp8/index.md b/Samples/csharp8/index.md deleted file mode 100644 index 149be754b..000000000 --- a/Samples/csharp8/index.md +++ /dev/null @@ -1,22 +0,0 @@ -# What's new in C# 8.0 - exploration - -This exploration enables you to experiment with the features that have been released in C# 8.0, preview 2. You can try the code in the samples, modify it, and see the effects of using the new features in a variety of scenarios. - -You can try the following features that first appeared in preview 2: - -- [Pattern matching enhancements](./patterns.md): -- [Static local functions](static-local-functions.md) -- [Using declarations](using-declarations-ref-structs.md) - -The following language features first appeared in C# 8.0 preview 1: - -- [Nullable reference types](nullable-reference-types.md) -- [Asynchronous streams](asynchronous-streams.md) -- [Indices and ranges](indices-and-ranges.md) - -> *Note*: -> This exploration was last updated for C# 8.0 preview 2. - -You can try all of the features in the upcoming version of C# by stepping through the different sections of this tutorial. Or, you can select any of the preceding links to go to the feature you want to explore first. - -#### Next: [More patterns in more places »](./patterns.md) diff --git a/Samples/csharp8/indices-and-ranges-examples.md b/Samples/csharp8/indices-and-ranges-examples.md deleted file mode 100644 index df8e504b7..000000000 --- a/Samples/csharp8/indices-and-ranges-examples.md +++ /dev/null @@ -1,33 +0,0 @@ -# Indices and ranges - -The previous examples show two design decisions that caused much debate: - -- Ranges are *exclusive*, meaning the element at the last index is not in the range. -- The index `^0` is *the end* of the collection, not *the last element* in the collection. - -You can explore several examples that highlight reasons for these decisions on this page. Start by defining an array of elements, and three variables to use as indices into that array. All these samples are tied together. You can edit the values of `x`, `y` and `z`, or the expressions in the other windows, click the *indices-ranges* button and see the results. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_CreateRange --session indices-ranges -``` - -The choice that `^0` matches *the end* means math using the `Length` property is reasonable: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_MathWithLength --session indices-ranges -``` - -Ranges are *exclusive*, making consecutive, disjoint sequences clear: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_Disjoint --session indices-ranges -``` - -The choice of end means removing the same number of elements from each end of a sequence is obvious: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_RemoveFromEnds --session indices-ranges -``` - -Incomplete ranges can assume `0` or `^0` for the missing index. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_IncompleteRanges --session indices-ranges -``` - -#### Next: [Indices and ranges scenario »](./indices-and-ranges-scenario.md) Previous: [Indices and ranges «](./nullable-fix-class.md) Home: [Home](index.md) diff --git a/Samples/csharp8/indices-and-ranges-scenario.md b/Samples/csharp8/indices-and-ranges-scenario.md deleted file mode 100644 index 2ac0ce24a..000000000 --- a/Samples/csharp8/indices-and-ranges-scenario.md +++ /dev/null @@ -1,10 +0,0 @@ -# Indices and ranges - -This final example shows taking a moving average on a sequence of numbers that approaches a maximum value. As you move through the range looking at small samples of data, you can get a clear picture of general shape of the data. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_MovingAverage -``` - -Consider using this new syntax whenever you are working with subsets of data. - -#### Previous: [Indices and ranges examples «](./indices-and-ranges-examples.md) Home: [Home](index.md) diff --git a/Samples/csharp8/indices-and-ranges.md b/Samples/csharp8/indices-and-ranges.md deleted file mode 100644 index 518934d3a..000000000 --- a/Samples/csharp8/indices-and-ranges.md +++ /dev/null @@ -1,56 +0,0 @@ -# Indices and ranges - -Ranges and indices provide a succinct syntax for specifying subranges in an array, `Span`, or `ReadOnlySpan`. - -## Language support for indices and ranges - -You can specify an index **from the end**. You specify **from the end** using the `^` operator. You are familiar with `array[2]` meaning the element "2 from the start". Now, `array[^2]` means the element "2 from the end". The index `^0` means "the end", or the index that follows the last element. - -You can specify a **range** with the **range operator**: `..`. For example, `0..^0` specifies the entire range of the array: 0 from the start up to, but not including 0 from the end. Either operand may use "from the start" or "from the end". Furthermore, either operand may be omitted. The defaults are `0` for the start index, and `^0` for the end index. - -Let's look at a few examples. Consider the following array, annotated with its index from the start and from the end: - -```csharp -private string[] words = new string[] -{ - // index from start index from end - "The", // 0 ^9 - "quick", // 1 ^8 - "brown", // 2 ^7 - "fox", // 3 ^6 - "jumped", // 4 ^5 - "over", // 5 ^4 - "the", // 6 ^3 - "lazy", // 7 ^2 - "dog" // 8 ^1 -}; -``` - -The index of each element reinforces the concept of "from the start", and "from the end", and that ranges are exclusive of the end of the range. The "start" of the entire array is the first element. The "end" of the entire array is *past* the last element. - -You can retrieve the last word with the `^1` index: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_LastIndex -``` - -The following code creates a subrange with the words "quick", "brown", and "fox". It includes `words[1]` through `words[3]`. The element `words[4]` is not in the range. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_Range -``` - -The following code creates a subrange with "lazy" and "dog". It includes `words[^2]` and `words[^1]`. The end index `words[^0]` is not included: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_LastRange -``` - -The following examples create ranges that are open ended for the start, end, or both: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_PartialRanges -``` - -You can also declare ranges or indices as variables. The variable can then be used inside the `[` and `]` characters: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/IndicesAndRanges.cs --region IndicesAndRanges_RangeIndexTypes -``` - -#### Next: [Indices and ranges examples »](./indices-and-ranges-examples.md) Previous: [Asynchronous streams «](./asynchronous-streams.md) Home: [Home](index.md) diff --git a/Samples/csharp8/nullable-fix-class.md b/Samples/csharp8/nullable-fix-class.md deleted file mode 100644 index 54fcdc12c..000000000 --- a/Samples/csharp8/nullable-fix-class.md +++ /dev/null @@ -1,40 +0,0 @@ -# Declare nullable property - -The following code shows the existing definition for the `Person` class, displayed in a **nullable enabled** context. You should see the warning in the constructor that takes `firstName` and `lastName` arguments, but does not set the `MiddleName` property. - -On this page, you're going to modify the `Person` class in different ways to remove the warning. Farther down this page, you see the code that uses the `Person` class. Different changes to the `Person` class will generate different warnings in those later samples. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/NullableReferences.cs --region Nullable_PersonDefinition --session nullable -``` - -Think about your design intent. Should `MiddleName` always be non-null? Or does it better reflect your design for `MiddleName` to be `null` for some people? You can either initialize the `MiddleName` property, or change `string` to `string?` in the declaration: - -```csharp -public string? MiddleName { get; set; } -``` - -After you've made that change, look at the two code snippets that follow. Once the environment analyzes your code, you'll see warnings. Examine each and adjust the code to fix the warnings. Start with the `GetLengthOfMiddleName` method: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/NullableReferences.cs --region Nullable_GetLengthMethod --session nullable -``` - -If needed, fix the code that calls it: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/NullableReferences.cs --region Nullable_Usage --session nullable -``` - -One possible solution for `GetLengthOfName` would be the following code: - -```csharp - private static int GetLengthOfMiddleName(Person p) - { - string? middleName = p.MiddleName; - return middleName?.Length ?? 0; - } -``` - -You can let the *may be null* type flow among declarations. You can use the `?.` operator to safely dereference a *may be null* variable, combined with the `??` operator to provide a default value. - -Nullable reference types enable you to declare your intent around reference types: some should never be `null`, while for others `null` indicates a missing value. By declaring your intent, the compiler can help you enforce that intent. - -#### Next: [Asynchronous streams »](./asynchronous-streams.md) Previous: [Nullable reference types «](./nullable-reference-types.md) Home: [Home](index.md) diff --git a/Samples/csharp8/nullable-reference-types.md b/Samples/csharp8/nullable-reference-types.md deleted file mode 100644 index a4f86d5a5..000000000 --- a/Samples/csharp8/nullable-reference-types.md +++ /dev/null @@ -1,28 +0,0 @@ -# Nullable reference types - -C# 8/0 defines new syntax and static analysis rules so that you can declare your design intent regarding the nullability of any reference variable. - -Earlier, C# compiled all code in a **nullable oblivious** context. The compiler had no information about your design intent: Did you expect every variable to be initialized and assigned to non-null values? Or was `null` a valid value that you should check before dereferencing the variable? In a **nullable oblivious** context, the compiler does not issue any nullability warnings, and assumes you understand your design intent throughout your codebase. - -Beginning with C# 8.0, the compiler defaults to a **nullable disabled** context. You can also opt in to a **nullable enabled** context. A **nullable disabled** context behaves the same as the earlier **nullable oblivious** behavior. You can opt in to a **nullable enabled** context. In a **nullable enabled** context, any variable of a reference type is a **non-nullable reference**. You can declare reference type variables with the `?` suffix to indicate that a variable's value may be null. - -In a **nullable enabled** context, the compiler uses static analysis to enforce these rules: - -- A non-nullable reference must be initialized to a non-null value. -- A non-nullable reference cannot be assigned to a variable that may be null. -- A nullable reference may only be dereferenced safely when static analysis determines its value cannot be null. - -There may be situations where you know a variable is not null, even though static analysis cannot prove it. For those situations, you can use the null forgiveness operator, `!`, to declare that the variable is not null. - -The nullable enabled context provides more safety against null reference exceptions. You can declare your intent, and the compiler issues warnings when your code isn't consistent with that declared intent. - -## Explore a basic scenario - -Try running the following code block: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/NullableReferences.cs --region Nullable_Usage -``` - -**Note:** That block throws a `NullReferenceException` because the `MiddleName` property is null for this person. Someone coded this error and the compiler didn't provide any helpful information to inform them that this code wasn't safe. In the next pages, you'll explore how nullable reference types would have avoided this bug. - -#### Next: [Declare design intent »](./nullable-fix-class.md) Previous: [Using declarations «](./using-declarations-ref-structs.md) Home: [Home](index.md) diff --git a/Samples/csharp8/patterns-occupancy.md b/Samples/csharp8/patterns-occupancy.md deleted file mode 100644 index fe9008a87..000000000 --- a/Samples/csharp8/patterns-occupancy.md +++ /dev/null @@ -1,35 +0,0 @@ -# Add occupancy pricing - -The toll authority wants to encourage vehicles to travel at maximum capacity. They've decided to charge more when vehicles have fewer passengers, and encourage full vehicles by offering lower pricing: - -- Cars and taxis with no passengers pay an extra $0.50. -- Cars and taxis with two passengers get a 0.50 discount. -- Cars and taxis with three or more passengers get a $1.00 discount. -- Buses that are less than 50% full pay an extra $2.00. -- Buses that are more than 90% full get a $1.00 discount. - -These rules can be implemented using the **property pattern** in the same switch expression. The property pattern examines properties of the object once the type has been determined. The single case for a `Car` expands to four different cases, as does the single case for `Taxi`: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_CarTaxiOccupancy -``` - -The first three cases test the type as a `Car`, then check the value of the `Passengers` property. If both match, that expression is evaluated and returned. Next, implement the occupancy rules by expanding the cases for buses, as shown in the following example: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_BusOccupancy -``` - -The toll authority isn't concerned with the number of passengers in the delivery trucks. Instead, they charge more based on the weight class of the trucks. Trucks over 5000 lbs are charged an extra $5.00. Light trucks under 3000 lbs are given a $2.00 discount. That rule is implemented with the following code: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_DeliveryTruckWeight -``` - -Many of these switch arms are examples of **recursive patterns**. For example, `Car { Passengers: 1}` shows a constant pattern inside a property pattern. - -You can make this code less repetitive by using nested switches. The `Car` and `Taxi` both have four different arms in the preceding examples. In both cases, you can create a type pattern that feeds into a property pattern. This technique is shown in the following code: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_ChainedPatterns -``` - -In the preceding sample, using a recursive expression means you don't repeat the `Car` and `Taxi` arms containing child arms that test the property value. This technique isn't used for the `Bus` and `DeliveryTruck` arms because those arms are testing ranges for the property, not discrete values. - -#### Next: [Add peak time pricing »](./patterns-peakpricing.md) Previous: [Type pattern «](./patterns-types.md) Home: [Home](index.md) diff --git a/Samples/csharp8/patterns-peakpricing.md b/Samples/csharp8/patterns-peakpricing.md deleted file mode 100644 index 9d973a9a8..000000000 --- a/Samples/csharp8/patterns-peakpricing.md +++ /dev/null @@ -1,154 +0,0 @@ -# Add peak pricing - -For the final feature, the toll authority wants to add time sensitive peak pricing. During the morning and evening rush hours, the tolls are doubled. That rule only affects traffic in one direction: inbound to the city in the morning, and outbound in the evening rush hour. During other times during the workday, tolls increase by 50%. Late night and early morning, tolls are reduced by 25%. During the weekend, it's the normal rate, regardless of the time. - -You'll use pattern matching for this feature, but you'll integrate it with other techniques. You could build a single pattern match expression that would account for all the combinations of direction, day of the week, and time. The result would be a complicated expression. It would be hard to read and difficult to understand. That makes it hard to ensure correctness. Instead, combine those methods to build a tuple of values that concisely describes all those states. Then use pattern matching to calculate a multiplier for the toll. The tuple contains three discrete conditions: - -- The day is either a weekday or a weekend. -- The band of time when the toll is collected. -- The direction is into the city or out of the city - -The following table shows the combinations of input values and the peak pricing multiplier: - -| Day | Time | Direction | Premium | -| ---------- | ------------ | --------- |--------:| -| Weekday | morning rush | inbound | x 2.00 | -| Weekday | morning rush | outbound | x 1.00 | -| Weekday | daytime | inbound | x 1.50 | -| Weekday | daytime | outbound | x 1.50 | -| Weekday | evening rush | inbound | x 1.00 | -| Weekday | evening rush | outbound | x 2.00 | -| Weekday | overnight | inbound | x 0.75 | -| Weekday | overnight | outbound | x 0.75 | -| Weekend | morning rush | inbound | x 1.00 | -| Weekend | morning rush | outbound | x 1.00 | -| Weekend | daytime | inbound | x 1.00 | -| Weekend | daytime | outbound | x 1.00 | -| Weekend | evening rush | inbound | x 1.00 | -| Weekend | evening rush | outbound | x 1.00 | -| Weekend | overnight | inbound | x 1.00 | -| Weekend | overnight | outbound | x 1.00 | - -There are 16 different combinations of the three variables. By combining some of the conditions, you'll simplify the final switch expression. - -The system that collects the tolls uses a structure for the time when the toll was collected. Build member methods that create the variables from the preceding table. The following function uses a pattern matching switch expression to express whether a represents a weekend or a weekday: - -```csharp -private static bool IsWeekDay(DateTime timeOfToll) => - timeOfToll.DayOfWeek switch - { - DayOfWeek.Monday => true, - DayOfWeek.Tuesday => true, - DayOfWeek.Wednesday => true, - DayOfWeek.Thursday => true, - DayOfWeek.Friday => true, - DayOfWeek.Saturday => false, - DayOfWeek.Sunday => false - }; -``` - -That method works, but it's repetitious. You can simplify it, as shown in the following code: - -```csharp -private static bool IsWeekDay(DateTime timeOfToll) => - timeOfToll.DayOfWeek switch - { - DayOfWeek.Saturday => false, - DayOfWeek.Sunday => false, - _ => true - }; -``` - -Next, add a similar function to categorize the time into the blocks: - -```csharp -private enum TimeBand -{ - MorningRush, - Daytime, - EveningRush, - Overnight -} - -private static GetTimeBand GetTimeBand(DateTime timeOfToll) -{ - int hour = timeOfToll.Hour; - if (hour < 6) - return TimeBand.Overnight; - else if (hour < 10) - return TimeBand.MorningRush; - else if (hour < 16) - return TimeBand.Daytime; - else if (hour < 20) - return TimeBand.EveningRush; - else - return TimeBand.Overnight; -} -``` - -The previous method doesn't use pattern matching. It's clearer using a familiar cascade of `if` statements. You do add a private `enum` to convert each range of time to a discrete value. - -After you create those methods, you can use another `switch` expression with the **tuple pattern** to calculate the pricing premium. You could build a `switch` expression with all 16 arms: - -```csharp -public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) => - (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch - { - (true, TimeBand.MorningRush, true) => 2.00m, - (true, TimeBand.MorningRush, false) => 1.00m, - (true, TimeBand.Daytime, true) => 1.50m, - (true, TimeBand.Daytime, false) => 1.50m, - (true, TimeBand.EveningRush, true) => 1.00m, - (true, TimeBand.EveningRush, false) => 2.00m, - (true, TimeBand.Overnight, true) => 0.75m, - (true, TimeBand.Overnight, false) => 0.75m, - (false, TimeBand.MorningRush, true) => 1.00m, - (false, TimeBand.MorningRush, false) => 1.00m, - (false, TimeBand.Daytime, true) => 1.00m, - (false, TimeBand.Daytime, false) => 1.00m, - (false, TimeBand.EveningRush, true) => 1.00m, - (false, TimeBand.EveningRush, false) => 1.00m, - (false, TimeBand.Overnight, true) => 1.00m, - (false, TimeBand.Overnight, false) => 1.00m, - }; -``` - -The above code works, but it can be simplified. All eight combinations for the weekend have the same toll. You can replace all eight with the following line: - -```csharp -(false, _, _) => 1.0m, -``` - -Both inbound and outbound traffic have the same multiplier during the weekday daytime and overnight hours. Those four switch arms can be replaced with the following two lines: - -```csharp -(true, TimeBand.Overnight, _) => 0.75m, -(true, TimeBand.Daytime, _) => 1.5m, -``` - -The code should look like the following code after those two changes: - -```csharp -public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) => - (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch - { - (true, TimeBand.MorningRush, true) => 2.00m, - (true, TimeBand.MorningRush, false) => 1.00m, - (true, TimeBand.Daytime, _) => 1.50m, - (true, TimeBand.EveningRush, true) => 1.00m, - (true, TimeBand.EveningRush, false) => 2.00m, - (true, TimeBand.Overnight, _) => 0.75m, - (false, _, _) => 1.00m, - }; -``` - -Finally, you can remove the two rush hour times that pay the regular price. Once you remove those arms, you can replace the `false` with a discard (`_`) in the final switch arm. You'll have the following finished method: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_PeakTime -``` - -This example highlights one of the advantages of pattern matching: the pattern branches are evaluated in order. If you rearrange them so that an earlier branch handles one of your later cases, the compiler warns you about the unreachable code. Those language rules made it easier to do the preceding simplifications with confidence that the code didn't change. - -Pattern matching makes some types of code more readable and offers an alternative to object-oriented techniques when you can't add code to your classes. The cloud is causing data and functionality to live apart. The *shape* of the data and the *operations* on it aren't necessarily described together. In this tutorial, you consumed existing data in entirely different ways from its original function. Pattern matching gave you the ability to write functionality that overrode those types, even though you couldn't extend them. - -#### Next: [static local functions »](./static-local-functions.md) Previous: [Add occupancy pricing «](./patterns-occupancy.md) Home: [Home](index.md) diff --git a/Samples/csharp8/patterns-types.md b/Samples/csharp8/patterns-types.md deleted file mode 100644 index 73fcf95f5..000000000 --- a/Samples/csharp8/patterns-types.md +++ /dev/null @@ -1,28 +0,0 @@ -# Pattern matching designs - -This scenario highlights the kinds of problems that pattern matching is well-suited to solve: - -- The objects you need to work with aren't in an object hierarchy that matches your goals. You may be working with classes that are part of unrelated systems. -- The functionality you're adding isn't part of the core abstraction for these classes. The toll paid by a vehicle *changes* for different types of vehicles, but the toll isn't a core function of the vehicle. - -When the *shape* of the data and the *operations* on that data are not described together, the pattern matching features in C# make it easier to work with. - -## Implement the basic toll calculations - -The most basic toll calculation relies only on the vehicle type: - -- A `Car` is $2.00. -- A `Taxi` is $3.50. -- A `Bus` is $5.00. -- A `DeliveryTruck` is $10.00 - -Create a new `TollCalculator` class, and implement pattern matching on the vehicle type to get the toll amount. The following code shows the initial implementation of the `TollCalculator` class's `CalculateToll`. - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/Patterns.cs --region Pattern_CalculateToll -``` - -The preceding code uses a **switch expression** (not the same as a [`switch`](../language-reference/keywords/switch.md) statement) that tests the **type pattern**. A **switch expression** begins with the variable, `vehicle` in the preceding code, followed by the `switch` keyword. Next comes all the **switch arms** inside curly braces. The `switch` expression makes other refinements to the syntax that surrounds the `switch` statement. The `case` keyword is omitted, and the result of each arm is an expression. The last two arms show a new language feature. The `{ }` case matches any non-null object that didn't match an earlier arm. This arm catches any incorrect types passed to this method. The `{ }` case must follow the cases for each vehicle type. If the order were reversed, the `{ }` case would take precedence. Finally, the `null` pattern detects when a `null` is passed to this method. The `null` pattern can be last because the other type patterns match only a non-null object of the correct type. - -You're starting to see how patterns can help you create algorithms where the code and the data are separate. The `switch` expression tests the type and produces different values based on the results. That's only the beginning. - -#### Next: [Add occupancy pricing »](./patterns-occupancy.md) Previous: [Pattern Matching «](./patterns.md) Home: [Home](index.md) diff --git a/Samples/csharp8/patterns.md b/Samples/csharp8/patterns.md deleted file mode 100644 index d12fd69ee..000000000 --- a/Samples/csharp8/patterns.md +++ /dev/null @@ -1,63 +0,0 @@ -# More patterns in more places - -**Pattern matching** gives tools to provide shape-dependent functionality across related but different kinds of data. C# 7.0 introduced syntax for type patterns and constant patterns by using the [`is`](../language-reference/keywords/is.md) expression and the [`switch`](../language-reference/keywords/switch.md) statement. These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed. - -C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Consider these features when your data and functionality are separate. Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. These techniques provide another way to express designs. - -## Scenarios for pattern matching - -Modern development often includes integrating data from multiple sources and presenting information and insights from that data in a single cohesive application. You and your team won't have control or access for all the types that represent the incoming data. - -The classic object-oriented design would call for creating types in your application that represent each data type from those multiple data sources. Then, your application would work with those new types, build inheritance hierarchies, create virtual methods, and implement abstractions. Those techniques work, and sometimes they are the best tools. Other times you can write less code. You can write more clear code using techniques that separate the data from the operations that manipulate that data. - -In this tutorial, you'll create and explore an application that takes incoming data from several external sources for a single scenario. You'll see how **pattern matching** provides an efficient way to consume and process that data in ways that weren't part of the original system. - -Consider a major metro area that is using tolls and peak time pricing to manage traffic. You write an application that calculates tolls for a vehicle based on its type. Later enhancements incorporate pricing based on the number of occupants in the vehicle. Further enhancements add pricing based on the time and the day of the week. - -From that brief description, you may have quickly sketched out an object hierarchy to model this system. However, your data is coming from multiple sources like other vehicle registration management systems. These systems provide different classes to model that data and you don't have a single object model you can use. In this tutorial, you'll use these simplified classes to model for the vehicle data from these external systems, as shown in the following three short code snippets. - -The `Car` class comes from the `ConsumerVehicleRegistration` system: - -```csharp -namespace ConsumerVehicleRegistration -{ - public class Car - { - public int Passengers { get; set; } - } -} -``` - -The `DeliveryTruck` class comes from the `CommercialRegistration` system: - -```csharp -namespace CommercialRegistration -{ - public class DeliveryTruck - { - public int GrossWeightClass { get; set; } - } -} -``` - -And the `LiveryRegistration` system manages the `Taxi` and `Bus` classes: - -```csharp -namespace LiveryRegistration -{ - public class Taxi - { - public int Fares { get; set; } - } - - public class Bus - { - public int Capacity { get; set; } - public int Riders { get; set; } - } -} -``` - -These classes are from different systems, and are in different namespaces. No common base class, other than `System.Object` can be leveraged. Object Oriented techniques will not serve for this problem. - -#### Next: [Explore type patterns »](./patterns-types.md) Home: [Home](index.md) diff --git a/Samples/csharp8/static-local-functions.md b/Samples/csharp8/static-local-functions.md deleted file mode 100644 index f9e26d75f..000000000 --- a/Samples/csharp8/static-local-functions.md +++ /dev/null @@ -1,24 +0,0 @@ -# Static local functions - -You can now add the `static` modifier to local functions to ensure that local function doesn't capture (reference) any variables from the enclosing scope. Doing so generates `CS8421`, "A static local function can't contain a reference to \." - -Typically, any resources declared or used in a function are released when the function exits. However, when a function *captures* a variable that is declared in an enclosing scope, that resource has a lifetime that extends beyond the function itself. In many scenarios, this isn't an issue. In others, capturing and preventing the release of resources can impact performance. Declaring local functions as `static` ensures this doesn't happen. - -Consider the following code. The local function `LocalFunction` accesses the variable `y`, declared in the enclosing scope (the method `M`). Therefore, `LocalFunction` can't be declared with the `static` modifier: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/StaticLocalFunctions.cs --region LocalFunction_Counting -``` - -The local iterator method *captures* the parameters `start` and `end`. Add the `static` modifier to see the compiler generated warning. You'll need to declare arguments to the local function so that those values aren't captured. Make the changes shown below to get the warning removed: - -```csharp -static IEnumerable localCounter(int first, int endLocation) -{ - for (int i = first; i < endLocation; i++) - yield return i; -} -``` - -The sample should compile and run correctly. - -#### Next: [disposable ref structs and using declarations »](using-declarations-ref-structs.md) Previous: [Add peak pricing «](./patterns-peakpricing.md) Home: [Home](index.md) diff --git a/Samples/csharp8/using-declarations-ref-structs.md b/Samples/csharp8/using-declarations-ref-structs.md deleted file mode 100644 index 559e42225..000000000 --- a/Samples/csharp8/using-declarations-ref-structs.md +++ /dev/null @@ -1,21 +0,0 @@ -# using declarations - -A **using declaration** is a variable declaration preceded by the `using` keyword. It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope. The `using` block has always been recommended for local variables when the type implements `IDisposable`. The following example shows a hypothetical use of two objects that implement `IDisposable`. Run the code and you'll see the `ResourceHog` writes a message when it is being disposed: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/UsingDeclarationsRefStruct.cs --region Using_Block -``` - -In the preceding example, each object is disposed when the closing brace for its `using` statement is reached. The new `using` declaration generates code that automatically disposes an object at the end of the enclosing block. That results in cleaner code that is easier to understand: - -```cs --project ./ExploreCsharpEight/ExploreCsharpEight.csproj --source-file ./ExploreCsharpEight/UsingDeclarationsRefStruct.cs --region Using_Declaration -``` - -In the preceding example, the resources are disposed at the end of the block. Run it to see the results, which should model the previous sample. - -In both cases, the compiler generates the call to `Dispose()`. The compiler generates an error if the expression in the using statement is not disposable. - -# Disposable ref structs - -A `struct` declared with the `ref` modifier may not implement any interfaces and so cannot implement `System.IDisposable`. Therefore, to enable a `ref struct` to be disposed, it must have an accessible `void Dispose()` method. This also applies to `readonly ref struct` declarations. These types can now release resources in `using` declarations or `using` blocks. - -#### Next: [nullable reference types »](./nullable-reference-types.md) Previous: [Static local functions «](./static-local-functions.md) Home: [Home](index.md) diff --git a/TryDotNet.sln b/TryDotNet.sln new file mode 100644 index 000000000..6b382c9e9 --- /dev/null +++ b/TryDotNet.sln @@ -0,0 +1,69 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{77B0B387-FEA7-4FBC-AC04-76CB4A16F758}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AC4DCFB8-D55F-4B1C-8B74-5517831CB65E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TryDotNet", "src\Microsoft.TryDotNet\Microsoft.TryDotNet.csproj", "{FDB0E498-AF5C-44B5-93DD-CA77BB26D195}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TryDotNet.Tests", "src\Microsoft.TryDotNet.Tests\Microsoft.TryDotNet.Tests.csproj", "{37A560A7-8A96-400C-A12E-98248EDD1C29}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TryDotNet.WasmRunner", "src\Microsoft.TryDotNet.WasmRunner\Microsoft.TryDotNet.WasmRunner.csproj", "{4D0D85BE-2427-4D8A-86CD-C4C0991794EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TryDotNet.SimulatorGenerator", "src\Microsoft.TryDotNet.SimulatorGenerator\Microsoft.TryDotNet.SimulatorGenerator.csproj", "{8630032F-C2A1-4A76-928A-2C828804765B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TryDotNet.IntegrationTests", "src\Microsoft.TryDotNet.IntegrationTests\Microsoft.TryDotNet.IntegrationTests.csproj", "{C8F85960-8E0B-404D-8E73-6C7EEBF727BB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8D0F8011-2504-432D-BA70-3D92656747D3}" + ProjectSection(SolutionItems) = preProject + azure-pipelines-CI.yml = azure-pipelines-CI.yml + azure-pipelines.yml = azure-pipelines.yml + Directory.Packages.props = Directory.Packages.props + Dockerfile = Dockerfile + global.json = global.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDB0E498-AF5C-44B5-93DD-CA77BB26D195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDB0E498-AF5C-44B5-93DD-CA77BB26D195}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDB0E498-AF5C-44B5-93DD-CA77BB26D195}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDB0E498-AF5C-44B5-93DD-CA77BB26D195}.Release|Any CPU.Build.0 = Release|Any CPU + {37A560A7-8A96-400C-A12E-98248EDD1C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37A560A7-8A96-400C-A12E-98248EDD1C29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37A560A7-8A96-400C-A12E-98248EDD1C29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37A560A7-8A96-400C-A12E-98248EDD1C29}.Release|Any CPU.Build.0 = Release|Any CPU + {4D0D85BE-2427-4D8A-86CD-C4C0991794EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D0D85BE-2427-4D8A-86CD-C4C0991794EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D0D85BE-2427-4D8A-86CD-C4C0991794EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D0D85BE-2427-4D8A-86CD-C4C0991794EF}.Release|Any CPU.Build.0 = Release|Any CPU + {8630032F-C2A1-4A76-928A-2C828804765B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8630032F-C2A1-4A76-928A-2C828804765B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8630032F-C2A1-4A76-928A-2C828804765B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8630032F-C2A1-4A76-928A-2C828804765B}.Release|Any CPU.Build.0 = Release|Any CPU + {C8F85960-8E0B-404D-8E73-6C7EEBF727BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8F85960-8E0B-404D-8E73-6C7EEBF727BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8F85960-8E0B-404D-8E73-6C7EEBF727BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8F85960-8E0B-404D-8E73-6C7EEBF727BB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FDB0E498-AF5C-44B5-93DD-CA77BB26D195} = {77B0B387-FEA7-4FBC-AC04-76CB4A16F758} + {37A560A7-8A96-400C-A12E-98248EDD1C29} = {AC4DCFB8-D55F-4B1C-8B74-5517831CB65E} + {4D0D85BE-2427-4D8A-86CD-C4C0991794EF} = {77B0B387-FEA7-4FBC-AC04-76CB4A16F758} + {8630032F-C2A1-4A76-928A-2C828804765B} = {77B0B387-FEA7-4FBC-AC04-76CB4A16F758} + {C8F85960-8E0B-404D-8E73-6C7EEBF727BB} = {AC4DCFB8-D55F-4B1C-8B74-5517831CB65E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1D7DA1F1-417C-4A1F-9D78-05D3BF24D31C} + EndGlobalSection +EndGlobal diff --git a/TryDotNet.v3.ncrunchsolution b/TryDotNet.v3.ncrunchsolution new file mode 100644 index 000000000..13107d394 --- /dev/null +++ b/TryDotNet.v3.ncrunchsolution @@ -0,0 +1,8 @@ + + + True + True + True + True + + \ No newline at end of file diff --git a/azure-pipelines-CI.yml b/azure-pipelines-CI.yml new file mode 100644 index 000000000..f7c94e2a2 --- /dev/null +++ b/azure-pipelines-CI.yml @@ -0,0 +1,191 @@ +# azure-pipelines-CI.yml +parameters: +- name: SkipTests + displayName: Skip Tests + type: boolean + default: false + +# CI and PR triggers +trigger: +- main +- feature/* +- release/* +pr: +- main +- feature/* +- release/* + +resources: + repositories: + - repository: 1esPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +variables: +- template: /eng/common/templates/variables/pool-providers.yml@self +- template: /eng/templates/variables-template.yml@self +- name: additionalWindowsArgs + value: '' +- name: additionalLinuxArgs + value: '' + +extends: + # The pipeline extends the 1ES PT which will inject different SDL and compliance tasks. + # For non-production pipelines, use "Unofficial" as defined below. + # For productions pipelines, use "Official". + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + # Update the pool with your team's 1ES hosted pool. + pool: + name: $(DncEngInternalBuildPool) # Name of your hosted pool + image: 1es-windows-2022 # Name of the image in your pool. If not specified, first image of the pool is used + os: windows # OS of the image. This value cannot be a variable. Allowed values: windows, linux, macOS + + # Build Tags + customBuildTags: + - TryDotNet + - $(Build.BuildNumber) + + sdl: + git: + longpaths: true + tsa: + enabled: true + config: + repositoryName: 'dotnet-try' + codebaseName: 'dotnet-try' + instanceUrl: 'https://devdiv.visualstudio.com/' + projectName: 'DEVDIV' + iterationPath: 'DevDiv\\Future Backlog' + areaPath: 'DevDiv\\NET Developer Experience\\VS Testing\\Interactive' + notificationAliases: ['dotnetInteractive@microsoft.com'] + serviceTreeID: 'ab4d58de-beb3-4714-866c-1b46a48ea002' + allTools: true + policheck: + enabled: true + credscan: + enabled: true + suppressionsFile: $(Build.SourcesDirectory)/.config/credscan/Suppressions.json + + stages: + - stage: build + displayName: Build and Test + jobs: + - template: /eng/common/templates-official/jobs/jobs.yml@self + parameters: + enableMicrobuild: true + enablePublishBuildArtifacts: true + enablePublishTestResults: true + enablePublishBuildAssets: true + enablePublishUsingPipelines: $(_PublishUsingPipelines) + jobs: + - job: Windows_NT + pool: + # TODO: Can we use latest or equivalent? + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals 1es-windows-2022 + os: windows + templateContext: + # This enables SDL scanning to be run once across all artifacts + outputParentDirectory: $(Build.SourcesDirectory)/artifacts + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/artifacts + artifactName: Windows_artifacts + displayName: Publish Windows artifacts + condition: always() + + variables: + - name: TRYDOTNET_PREBUILDS_PATH + value: $(Build.SourcesDirectory)\artifacts\trydotnet-prebuilds + - name: POCKETLOGGER_LOG_PATH + value: $(Build.SourcesDirectory)\artifacts\logs\pocketlogger.log + - name: RunIntegrationTests + value: true + # Enable signing for internal, non-PR builds + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-Symbol-Server-Pats + - name: _SignType + value: Real + - name: _DotNetPublishToBlobFeed + value: false + - name: _BuildArgs + value: /p:SignType=$(_SignType) + /p:DotNetSignType=$(_SignType) + /p:MicroBuild_SigningEnabled=true + /p:OverridePackageSource=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json + /p:TeamName=$(_TeamName) + /p:DotNetPublishToBlobFeed=false + /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) + /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + # else + - ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + steps: + - task: UseDotNet@2 + displayName: Install SDK 9.0.111 + inputs: + packageType: sdk + version: 9.0.111 + - template: /eng/templates/build-and-test-job-windows-templates.yml@self + parameters: + buildConfig: $(_BuildConfig) + skipTests: $(SkipTests) + buildArgs: $(_BuildArgs) + additionalArgs: $(additionalWindowsArgs) + - template: /eng/common/templates-official/jobs/jobs.yml@self + parameters: + enableMicrobuild: true + enablePublishBuildArtifacts: true + enablePublishTestResults: true + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + jobs: + - job: Linux + pool: + name: $(DncEngInternalBuildPool) + os: linux + demands: ImageOverride -equals 1es-mariner-2 + templateContext: + # This enables SDL scanning to be run once across all artifacts + outputParentDirectory: $(Build.SourcesDirectory)/artifacts + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/artifacts + artifactName: Linux_artifacts + displayName: Publish Linux artifacts + condition: always() + variables: + - name: TRYDOTNET_PREBUILDS_PATH + value: $(Build.SourcesDirectory)/artifacts/trydotnet-prebuilds + - name: POCKETLOGGER_LOG_PATH + value: $(Build.SourcesDirectory)/artifacts/logs/pocketlogger.log + # Enable signing for internal, non-PR builds + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + /p:DotNetSignType=$(_SignType) + /p:OverridePackageSource=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json + /p:TeamName=$(_TeamName) + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + # else + - ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + steps: + - template: /eng/templates/build-and-test-job-linux-templates.yml@self + parameters: + buildConfig: $(_BuildConfig) + skipTests: $(SkipTests) + additionalArgs: $(additionalLinuxArgs) diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..a7bb119b3 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,156 @@ +# azure-pipelines.yml +parameters: +- name: SkipTests + displayName: Skip Tests + type: boolean + default: false + +# CI and PR triggers +trigger: +- main +- feature/* +- release/* +pr: +- main +- feature/* +- release/* + +variables: + - template: /eng/templates/variables-template.yml@self + - name: system.debug + value: true + - name: additionalWindowsArgs + value: '--diag $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/diag.txt -p:m=1' + - name: additionalLinuxArgs + value: '--diag $(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/diag.txt' + +stages: +- stage: build + displayName: Build and Test + jobs: + - template: /eng/common/templates/jobs/jobs.yml + parameters: + enableMicrobuild: true + enablePublishBuildArtifacts: true + enablePublishTestResults: true + enablePublishBuildAssets: true + enablePublishUsingPipelines: $(_PublishUsingPipelines) + jobs: + - job: Windows_NT + pool: + ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: + name: NetCore-Public + demands: ImageOverride -equals windows.vs2022.amd64.open + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + name: NetCore1ESPool-Internal + demands: ImageOverride -equals windows.vs2022.amd64 + variables: + - name: RunIntegrationTests + value: true + - name: TRYDOTNET_PREBUILDS_PATH + value: $(Build.SourcesDirectory)\artifacts\trydotnet-prebuilds + - name: POCKETLOGGER_LOG_PATH + value: $(Build.SourcesDirectory)\artifacts\logs\pocketlogger.log + # Enable signing for internal, non-PR builds + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-Symbol-Server-Pats + - name: _SignType + value: Real + - name: _DotNetPublishToBlobFeed + value: false + - name: _BuildArgs + value: /p:SignType=$(_SignType) + /p:DotNetSignType=$(_SignType) + /p:MicroBuild_SigningEnabled=true + /p:OverridePackageSource=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json + /p:TeamName=$(_TeamName) + /p:DotNetPublishToBlobFeed=false + /p:DotNetPublishUsingPipelines=$(_PublishUsingPipelines) + /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + # else + - ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + steps: + - task: UseDotNet@2 + displayName: Install SDK 9.0.111 + inputs: + packageType: sdk + version: 9.0.111 + - template: /eng/templates/build-and-test-job-windows-templates.yml@self + parameters: + buildConfig: $(_BuildConfig) + skipTests: $(SkipTests) + buildArgs: $(_BuildArgs) + additionalArgs: $(additionalWindowsArgs) + - task: PublishBuildArtifacts@1 + displayName: Publish Windows artifacts + inputs: + pathToPublish: $(Build.SourcesDirectory)/artifacts/ + artifactName: Windows_artifacts + artifactType: container + condition: always() + + - template: /eng/common/templates/jobs/jobs.yml + parameters: + enableMicrobuild: true + enablePublishBuildArtifacts: true + enablePublishTestResults: true + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + jobs: + - job: Linux + pool: + vmImage: ubuntu-22.04 + variables: + - name: TRYDOTNET_PREBUILDS_PATH + value: $(Build.SourcesDirectory)/artifacts/trydotnet-prebuilds + - name: POCKETLOGGER_LOG_PATH + value: $(Build.SourcesDirectory)/artifacts/logs/pocketlogger.log + # Enable signing for internal, non-PR builds + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + /p:DotNetSignType=$(_SignType) + /p:OverridePackageSource=https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json + /p:TeamName=$(_TeamName) + /p:OfficialBuildId=$(BUILD.BUILDNUMBER) + # else + - ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: + - name: _SignType + value: Test + - name: _BuildArgs + value: /p:SignType=$(_SignType) + steps: + - template: /eng/templates/build-and-test-job-linux-templates.yml@self + parameters: + buildConfig: $(_BuildConfig) + skipTests: $(SkipTests) + additionalArgs: $(additionalLinuxArgs) + + - task: PublishBuildArtifacts@1 + displayName: Publish Linux artifacts + inputs: + pathToPublish: $(Build.SourcesDirectory)/artifacts/ + artifactName: Linux_artifacts + artifactType: container + condition: always() + +#---------------------------------------------------------------------------------------------------------------------# +# Post Build # +#---------------------------------------------------------------------------------------------------------------------# +- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: eng/common/templates/post-build/post-build.yml + parameters: + publishingInfraVersion: 3 + # Symbol validation is not entirely reliable as of yet, so should be turned off until https://github.com/dotnet/arcade/issues/2871 is resolved. + enableSymbolValidation: false + # SourceLink improperly looks for generated files. See https://github.com/dotnet/arcade/issues/3069 + enableSourceLinkValidation: false diff --git a/build-integration.cmd b/build-integration.cmd new file mode 100644 index 000000000..e1d7d18cc --- /dev/null +++ b/build-integration.cmd @@ -0,0 +1,5 @@ +@echo off +powershell -noprofile -executionPolicy RemoteSigned -file "%~dp0eng\build.ps1" -noDotnet %* +taskkill /F /IM dotnet.exe +rmdir /s /q ".\src\Microsoft.TryDotNet.IntegrationTests\bin" +rmdir /s /q ".\artifacts\bin\Microsoft.TryDotNet.IntegrationTests" diff --git a/build-js.cmd b/build-js.cmd new file mode 100644 index 000000000..843fc647f --- /dev/null +++ b/build-js.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -noprofile -executionPolicy RemoteSigned -file "%~dp0eng\build.ps1" -noDotnet %* diff --git a/build-js.sh b/build-js.sh new file mode 100755 index 000000000..b0b717d3e --- /dev/null +++ b/build-js.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd)" + +. "$scriptroot/eng/build.sh" --no-dotnet "$@" diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..3848dc97c --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -noprofile -executionPolicy RemoteSigned -file "%~dp0eng\build.ps1" -build -restore -binaryLog %* diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..48cd0feb1 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd)" + +. "$scriptroot/eng/build.sh" --build --restore --binaryLog "$@" diff --git a/eng/AfterSolutionBuild.targets b/eng/AfterSolutionBuild.targets new file mode 100644 index 000000000..59577accb --- /dev/null +++ b/eng/AfterSolutionBuild.targets @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/eng/CIBuild.cmd b/eng/CIBuild.cmd new file mode 100644 index 000000000..b15ea8b1b --- /dev/null +++ b/eng/CIBuild.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -noprofile -executionPolicy RemoteSigned -file "%~dp0build.ps1" -ci -restore -build -pack -publish -binaryLog %* diff --git a/eng/Publishing.props b/eng/Publishing.props new file mode 100644 index 000000000..bb4af0030 --- /dev/null +++ b/eng/Publishing.props @@ -0,0 +1,5 @@ + + + 3 + + diff --git a/eng/SignCheckExclusionsFile.txt b/eng/SignCheckExclusionsFile.txt new file mode 100644 index 000000000..90d6d4f9b --- /dev/null +++ b/eng/SignCheckExclusionsFile.txt @@ -0,0 +1,6 @@ +*bundle.js +blazor.*.js +editor.worker.js +interop.js +mono.js +trydotnet.min.js diff --git a/eng/Signing.props b/eng/Signing.props new file mode 100644 index 000000000..e250cb960 --- /dev/null +++ b/eng/Signing.props @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml new file mode 100644 index 000000000..7e52ef5e5 --- /dev/null +++ b/eng/Version.Details.xml @@ -0,0 +1,11 @@ + + + + + + + https://github.com/dotnet/arcade + 20ab70a74d52b68f4271bd946884e24049b14f83 + + + diff --git a/eng/Versions.props b/eng/Versions.props new file mode 100644 index 000000000..582175f7b --- /dev/null +++ b/eng/Versions.props @@ -0,0 +1,49 @@ + + + + false + false + false + + true + true + -parallel none + + + 1 + 0 + 0 + beta + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + Package for Try .NET + MIT + https://github.com/dotnet/try + false + https://github.com/dotnet/try + + + false + release + + + + 42424242.42 + <_BuildNumber>$(OfficialBuildId) + <_BuildNumberYY>$(_BuildNumber.Substring(2, 2)) + <_BuildNumberMM>$(_BuildNumber.Substring(4, 2)) + <_BuildNumberDD>$(_BuildNumber.Substring(6, 2)) + <_BuildNumberR>$(_BuildNumber.Substring(9)) + $([MSBuild]::Add($([MSBuild]::Add($([MSBuild]::Multiply($(_BuildNumberYY), 1000)), $([MSBuild]::Multiply($(_BuildNumberMM), 50)))), $(_BuildNumberDD))) + + $(MajorVersion).$(MinorVersion).$(VersionSuffixDateStamp) + $(VersionPrefix).$(_BuildNumberR) + $(VersionPrefix).$(_BuildNumberR) + $(AssemblyVersion) + + diff --git a/eng/build.ps1 b/eng/build.ps1 new file mode 100644 index 000000000..fbfe4e521 --- /dev/null +++ b/eng/build.ps1 @@ -0,0 +1,60 @@ +[CmdletBinding(PositionalBinding = $false)] +param ( + [string]$configuration = "Debug", + [switch]$noDotnet, + [switch]$test, + [Parameter(ValueFromRemainingArguments = $true)][String[]]$arguments +) + +Set-StrictMode -version 2.0 +$ErrorActionPreference = "Stop" + +try { + $repoRoot = Resolve-Path "$PSScriptRoot\.." + $npmDirs = @( + "src\microsoft-trydotnet", + "src\microsoft-trydotnet-editor", + "src\microsoft-trydotnet-styles", + "src\microsoft-learn-mock" + ) + foreach ($npmDir in $npmDirs) { + Push-Location "$repoRoot\$npmDir" + Write-Host "Building NPM in directory $npmDir" + npm ci + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + npm run buildProd + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + if ($test) { + Write-Host "Testing NPM in directory $npmDir" + npm run ciTest + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + + Pop-Location + } + + if (-Not $noDotnet) { + # promote switches to arguments + $arguments += "-configuration" + $arguments += $configuration + + # invoke regular build script + $buildScript = (Join-Path $PSScriptRoot "common\build.ps1") + Invoke-Expression "$buildScript $arguments" + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + + # playwright + if ($test) { + & $repoRoot\artifacts\bin\Microsoft.TryDotNet.IntegrationTests\$configuration\net9.0\playwright.ps1 install chromium + } + } +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 +} diff --git a/eng/build.sh b/eng/build.sh new file mode 100755 index 000000000..e1e1dcdd2 --- /dev/null +++ b/eng/build.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -e + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +# parse args +args="" +no_dotnet=false +run_tests=false + +# scan for `--test` or `--ci` switches +while [[ $# > 0 ]]; do + opt="$(echo "$1" | awk '{print tolower($0)}')" + case "$opt" in + --no-dotnet) + no_dotnet=true + ;; + --test|-t) + run_tests=true + ;; + *) + args="$args $1" + ;; + esac + shift +done + +# build and test NPM +npmDirs='src/microsoft-trydotnet + src/microsoft-trydotnet-editor + src/microsoft-trydotnet-styles + src/microsoft-learn-mock' +for npmDir in $npmDirs; +do + echo "Building NPM in directory $npmDir" + pushd $npmDir + npm ci + npm run buildProd + if [[ "$run_tests" == true ]]; then + echo "Testing NPM in directory $npmDir" + npm run ciTest + fi + popd +done + +if [[ "$no_dotnet" != true ]]; then + # invoke regular build script + . "$scriptroot/common/build.sh" $args +fi diff --git a/eng/cibuild.sh b/eng/cibuild.sh new file mode 100755 index 000000000..a5e2cd727 --- /dev/null +++ b/eng/cibuild.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd)" + +echo "Building this commit:" +git show --no-patch --pretty=raw HEAD + +. "$scriptroot/build.sh" --ci --restore --build --pack --binaryLog "$@" diff --git a/eng/common/BuildConfiguration/build-configuration.json b/eng/common/BuildConfiguration/build-configuration.json new file mode 100644 index 000000000..3d1cc8989 --- /dev/null +++ b/eng/common/BuildConfiguration/build-configuration.json @@ -0,0 +1,4 @@ +{ + "RetryCountLimit": 1, + "RetryByAnyError": false +} diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd new file mode 100644 index 000000000..56c2f25ac --- /dev/null +++ b/eng/common/CIBuild.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file diff --git a/eng/common/PSScriptAnalyzerSettings.psd1 b/eng/common/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 000000000..4c1ea7c98 --- /dev/null +++ b/eng/common/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,11 @@ +@{ + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidUsingInvokeExpression', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUseCmdletCorrectly', + 'PSStandardDSCFunctionsInResource', + 'PSUseIdenticalMandatoryParametersForDSC', + 'PSUseIdenticalParametersForDSC') +} \ No newline at end of file diff --git a/eng/common/README.md b/eng/common/README.md new file mode 100644 index 000000000..ff49c3715 --- /dev/null +++ b/eng/common/README.md @@ -0,0 +1,28 @@ +# Don't touch this folder + + uuuuuuuuuuuuuuuuuuuu + u" uuuuuuuuuuuuuuuuuu "u + u" u$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ + $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ + $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ + $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ + $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$" u" + "u """""""""""""""""" u" + """""""""""""""""""" + +!!! Changes made in this directory are subject to being overwritten by automation !!! + +The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 new file mode 100644 index 000000000..efa2fd72b --- /dev/null +++ b/eng/common/SetupNugetSources.ps1 @@ -0,0 +1,167 @@ +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing +# +# - task: PowerShell@2 +# displayName: Setup Private Feeds Credentials +# condition: eq(variables['Agent.OS'], 'Windows_NT') +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 +# arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)][string]$ConfigFile, + [Parameter(Mandatory = $true)][string]$Password +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +. $PSScriptRoot\tools.ps1 + +# Add source entry to PackageSources +function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { + $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") + + if ($packageSource -eq $null) + { + $packageSource = $doc.CreateElement("add") + $packageSource.SetAttribute("key", $SourceName) + $packageSource.SetAttribute("value", $SourceEndPoint) + $sources.AppendChild($packageSource) | Out-Null + } + else { + Write-Host "Package source $SourceName already present." + } + AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd +} + +# Add a credential node for the specified source +function AddCredential($creds, $source, $username, $pwd) { + # Looks for credential configuration for the given SourceName. Create it if none is found. + $sourceElement = $creds.SelectSingleNode($Source) + if ($sourceElement -eq $null) + { + $sourceElement = $doc.CreateElement($Source) + $creds.AppendChild($sourceElement) | Out-Null + } + + # Add the node to the credential if none is found. + $usernameElement = $sourceElement.SelectSingleNode("add[@key='Username']") + if ($usernameElement -eq $null) + { + $usernameElement = $doc.CreateElement("add") + $usernameElement.SetAttribute("key", "Username") + $sourceElement.AppendChild($usernameElement) | Out-Null + } + $usernameElement.SetAttribute("value", $Username) + + # Add the to the credential if none is found. + # Add it as a clear text because there is no support for encrypted ones in non-windows .Net SDKs. + # -> https://github.com/NuGet/Home/issues/5526 + $passwordElement = $sourceElement.SelectSingleNode("add[@key='ClearTextPassword']") + if ($passwordElement -eq $null) + { + $passwordElement = $doc.CreateElement("add") + $passwordElement.SetAttribute("key", "ClearTextPassword") + $sourceElement.AppendChild($passwordElement) | Out-Null + } + + $passwordElement.SetAttribute("value", $pwd) +} + +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $pwd) { + $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") + + Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." + + ForEach ($PackageSource in $maestroPrivateSources) { + Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key + AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -pwd $pwd + } +} + +function EnablePrivatePackageSources($DisabledPackageSources) { + $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") + ForEach ($DisabledPackageSource in $maestroPrivateSources) { + Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled by deleting it from disabledPackageSource" + # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries + $DisabledPackageSources.RemoveChild($DisabledPackageSource) + } +} + +if (!(Test-Path $ConfigFile -PathType Leaf)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +} + +if (!$Password) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Please supply a valid PAT' + ExitWithExitCode 1 +} + +# Load NuGet.config +$doc = New-Object System.Xml.XmlDocument +$filename = (Get-Item $ConfigFile).FullName +$doc.Load($filename) + +# Get reference to or create one if none exist already +$sources = $doc.DocumentElement.SelectSingleNode("packageSources") +if ($sources -eq $null) { + $sources = $doc.CreateElement("packageSources") + $doc.DocumentElement.AppendChild($sources) | Out-Null +} + +# Looks for a node. Create it if none is found. +$creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") +if ($creds -eq $null) { + $creds = $doc.CreateElement("packageSourceCredentials") + $doc.DocumentElement.AppendChild($creds) | Out-Null +} + +# Check for disabledPackageSources; we'll enable any darc-int ones we find there +$disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") +if ($disabledSources -ne $null) { + Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" + EnablePrivatePackageSources -DisabledPackageSources $disabledSources +} + +$userName = "dn-bot" + +# Insert credential nodes for Maestro's private feeds +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -pwd $Password + +# 3.1 uses a different feed url format so it's handled differently here +$dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") +if ($dotnet31Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password +} + +$dotnetVersions = @('5','6','7','8') + +foreach ($dotnetVersion in $dotnetVersions) { + $feedPrefix = "dotnet" + $dotnetVersion; + $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") + if ($dotnetSource -ne $null) { + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password + } +} + +$doc.Save($filename) \ No newline at end of file diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh new file mode 100644 index 000000000..d387c7eac --- /dev/null +++ b/eng/common/SetupNugetSources.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro's managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing. +# +# - task: Bash@3 +# displayName: Setup Private Feeds Credentials +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh +# arguments: $(Build.SourcesDirectory)/NuGet.config $Token +# condition: ne(variables['Agent.OS'], 'Windows_NT') +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +ConfigFile=$1 +CredToken=$2 +NL='\n' +TB=' ' + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ ! -f "$ConfigFile" ]; then + Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +fi + +if [ -z "$CredToken" ]; then + Write-PipelineTelemetryError -category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Please supply a valid PAT" + ExitWithExitCode 1 +fi + +if [[ `uname -s` == "Darwin" ]]; then + NL=$'\\\n' + TB='' +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + ConfigNodeHeader="" + PackageSourcesTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + + PackageSourcesNodeFooter="" + PackageSourceCredentialsTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile +fi + +PackageSources=() + +# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present +grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal') + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet3.1-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal-transport') +fi + +DotNetVersions=('5' '6' '7' '8') + +for DotNetVersion in ${DotNetVersions[@]} ; do + FeedPrefix="dotnet${DotNetVersion}"; + grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=("$FeedPrefix-internal") + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding $FeedPrefix-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=("$FeedPrefix-internal-transport") + fi +done + +# I want things split line by line +PrevIFS=$IFS +IFS=$'\n' +PackageSources+="$IFS" +PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') +IFS=$PrevIFS + +for FeedName in ${PackageSources[@]} ; do + # Check if there is no existing credential for this FeedName + grep -i "<$FeedName>" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding credentials for $FeedName." + + PackageSourceCredentialsNodeFooter="" + NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" + + sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile + fi +done + +# Re-enable any entries in disabledPackageSources where the feed name contains darc-int +grep -i "" $ConfigFile +if [ "$?" == "0" ]; then + DisabledDarcIntSources=() + echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" + DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') + for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do + if [[ $DisabledSourceName == darc-int* ]] + then + OldDisableValue="" + NewDisableValue="" + sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile + echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" + fi + done +fi diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 new file mode 100644 index 000000000..33a6f2d0e --- /dev/null +++ b/eng/common/build.ps1 @@ -0,0 +1,166 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug", + [string]$platform = $null, + [string] $projects, + [string][Alias('v')]$verbosity = "minimal", + [string] $msbuildEngine = $null, + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch][Alias('r')]$restore, + [switch] $deployDeps, + [switch][Alias('b')]$build, + [switch] $rebuild, + [switch] $deploy, + [switch][Alias('t')]$test, + [switch] $integrationTest, + [switch] $performanceTest, + [switch] $sign, + [switch] $pack, + [switch] $publish, + [switch] $clean, + [switch][Alias('bl')]$binaryLog, + [switch][Alias('nobl')]$excludeCIBinarylog, + [switch] $ci, + [switch] $prepareMachine, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $excludePrereleaseVS, + [switch] $nativeToolsOnMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file +# some computer has this env var defined (e.g. Some HP) +if($env:Platform) { + $env:Platform="" +} +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host " -clean Clean the solution" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" + Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +} + +. $PSScriptRoot\tools.ps1 + +function InitializeCustomToolset { + if (-not $restore) { + return + } + + $script = Join-Path $EngRoot 'restore-toolset.ps1' + + if (Test-Path $script) { + . $script + } +} + +function Build { + $toolsetBuildProj = InitializeToolset + InitializeCustomToolset + + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + + if ($projects) { + # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. + # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. + [string[]] $msbuildArgs = $properties + + # Resolve relative project paths into full paths + $projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';') + + $msbuildArgs += "/p:Projects=$projects" + $properties = $msbuildArgs + } + + MSBuild $toolsetBuildProj ` + $bl ` + $platformArg ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:Restore=$restore ` + /p:DeployDeps=$deployDeps ` + /p:Build=$build ` + /p:Rebuild=$rebuild ` + /p:Deploy=$deploy ` + /p:Test=$test ` + /p:Pack=$pack ` + /p:IntegrationTest=$integrationTest ` + /p:PerformanceTest=$performanceTest ` + /p:Sign=$sign ` + /p:Publish=$publish ` + @properties +} + +try { + if ($clean) { + if (Test-Path $ArtifactsDir) { + Remove-Item -Recurse -Force $ArtifactsDir + Write-Host 'Artifacts directory deleted.' + } + exit 0 + } + + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($ci) { + if (-not $excludeCIBinarylog) { + $binaryLog = $true + } + $nodeReuse = $false + } + + if ($nativeToolsOnMachine) { + $env:NativeToolsOnMachine = $true + } + if ($restore) { + InitializeNativeTools + } + + Build +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/build.sh b/eng/common/build.sh new file mode 100644 index 000000000..50af40cdd --- /dev/null +++ b/eng/common/build.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash + +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u + +# Stop script if command returns non-zero exit code. +# Prevents hidden errors caused by missing error code propagation. +set -e + +usage() +{ + echo "Common settings:" + echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + echo " --binaryLog Create MSBuild binary log (short: -bl)" + echo " --help Print help and exit (short: -h)" + echo "" + + echo "Actions:" + echo " --restore Restore dependencies (short: -r)" + echo " --build Build solution (short: -b)" + echo " --sourceBuild Source-build the solution (short: -sb)" + echo " Will additionally trigger the following actions: --restore, --build, --pack" + echo " If --configuration is not set explicitly, will also set it to 'Release'" + echo " --rebuild Rebuild solution" + echo " --test Run all unit tests in the solution (short: -t)" + echo " --integrationTest Run all integration tests in the solution" + echo " --performanceTest Run all performance tests in the solution" + echo " --pack Package build outputs into NuGet packages and Willow components" + echo " --sign Sign build outputs" + echo " --publish Publish artifacts (e.g. symbols)" + echo " --clean Clean the solution" + echo "" + + echo "Advanced settings:" + echo " --projects Project or solution file(s) to build" + echo " --ci Set when running on CI server" + echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" + echo " --prepareMachine Prepare machine for CI run, clean up processes after build" + echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." + echo "Arguments can also be passed in with a single hyphen." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +restore=false +build=false +source_build=false +rebuild=false +test=false +integration_test=false +performance_test=false +pack=false +publish=false +sign=false +public=false +ci=false +clean=false + +warn_as_error=true +node_reuse=true +binary_log=false +exclude_ci_binary_log=false +pipelines_log=false + +projects='' +configuration='' +prepare_machine=false +verbosity='minimal' +runtime_source_feed='' +runtime_source_feed_key='' + +properties='' +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -help|-h) + usage + exit 0 + ;; + -clean) + clean=true + ;; + -configuration|-c) + configuration=$2 + shift + ;; + -verbosity|-v) + verbosity=$2 + shift + ;; + -binarylog|-bl) + binary_log=true + ;; + -excludeCIBinarylog|-nobl) + exclude_ci_binary_log=true + ;; + -pipelineslog|-pl) + pipelines_log=true + ;; + -restore|-r) + restore=true + ;; + -build|-b) + build=true + ;; + -rebuild) + rebuild=true + ;; + -pack) + pack=true + ;; + -sourcebuild|-sb) + build=true + source_build=true + restore=true + pack=true + ;; + -test|-t) + test=true + ;; + -integrationtest) + integration_test=true + ;; + -performancetest) + performance_test=true + ;; + -sign) + sign=true + ;; + -publish) + publish=true + ;; + -preparemachine) + prepare_machine=true + ;; + -projects) + projects=$2 + shift + ;; + -ci) + ci=true + ;; + -warnaserror) + warn_as_error=$2 + shift + ;; + -nodereuse) + node_reuse=$2 + shift + ;; + -runtimesourcefeed) + runtime_source_feed=$2 + shift + ;; + -runtimesourcefeedkey) + runtime_source_feed_key=$2 + shift + ;; + *) + properties="$properties $1" + ;; + esac + + shift +done + +if [[ -z "$configuration" ]]; then + if [[ "$source_build" = true ]]; then configuration="Release"; else configuration="Debug"; fi +fi + +if [[ "$ci" == true ]]; then + pipelines_log=true + node_reuse=false + if [[ "$exclude_ci_binary_log" == false ]]; then + binary_log=true + fi +fi + +. "$scriptroot/tools.sh" + +function InitializeCustomToolset { + local script="$eng_root/restore-toolset.sh" + + if [[ -a "$script" ]]; then + . "$script" + fi +} + +function Build { + InitializeToolset + InitializeCustomToolset + + if [[ ! -z "$projects" ]]; then + properties="$properties /p:Projects=$projects" + fi + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:\"$log_dir/Build.binlog\"" + fi + + MSBuild $_InitializeToolset \ + $bl \ + /p:Configuration=$configuration \ + /p:RepoRoot="$repo_root" \ + /p:Restore=$restore \ + /p:Build=$build \ + /p:ArcadeBuildFromSource=$source_build \ + /p:Rebuild=$rebuild \ + /p:Test=$test \ + /p:Pack=$pack \ + /p:IntegrationTest=$integration_test \ + /p:PerformanceTest=$performance_test \ + /p:Sign=$sign \ + /p:Publish=$publish \ + $properties + + ExitWithExitCode 0 +} + +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 +fi + +if [[ "$restore" == true ]]; then + InitializeNativeTools +fi + +Build diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh new file mode 100644 index 000000000..1a02c0dec --- /dev/null +++ b/eng/common/cibuild.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.bionic b/eng/common/cross/arm/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/arm/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.jessie b/eng/common/cross/arm/sources.list.jessie new file mode 100644 index 000000000..4d142ac9b --- /dev/null +++ b/eng/common/cross/arm/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (sid) # UNSTABLE +deb http://ftp.debian.org/debian/ sid main contrib non-free +deb-src http://ftp.debian.org/debian/ sid main contrib non-free diff --git a/eng/common/cross/arm/sources.list.xenial b/eng/common/cross/arm/sources.list.xenial new file mode 100644 index 000000000..56fbb36a5 --- /dev/null +++ b/eng/common/cross/arm/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.zesty b/eng/common/cross/arm/sources.list.zesty new file mode 100644 index 000000000..ea2c14a78 --- /dev/null +++ b/eng/common/cross/arm/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.bionic b/eng/common/cross/arm64/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.xenial b/eng/common/cross/arm64/sources.list.xenial new file mode 100644 index 000000000..56fbb36a5 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.zesty b/eng/common/cross/arm64/sources.list.zesty new file mode 100644 index 000000000..ea2c14a78 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch new file mode 100644 index 000000000..2d2615619 --- /dev/null +++ b/eng/common/cross/armel/armel.jessie.patch @@ -0,0 +1,43 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 ++++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 +@@ -69,10 +69,10 @@ + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_val_compare_and_swap_8(addr, old, _new); +@@ -109,7 +109,7 @@ + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -148,7 +148,7 @@ + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -187,7 +187,7 @@ + return __sync_add_and_fetch_2(addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t*) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/armel/sources.list.jessie b/eng/common/cross/armel/sources.list.jessie new file mode 100644 index 000000000..3d9c3059d --- /dev/null +++ b/eng/common/cross/armel/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (jessie) # Stable +deb http://ftp.debian.org/debian/ jessie main contrib non-free +deb-src http://ftp.debian.org/debian/ jessie main contrib non-free diff --git a/eng/common/cross/armel/tizen/tizen.patch b/eng/common/cross/armel/tizen/tizen.patch new file mode 100644 index 000000000..ca7c7c1ff --- /dev/null +++ b/eng/common/cross/armel/tizen/tizen.patch @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) diff --git a/eng/common/cross/armv6/sources.list.buster b/eng/common/cross/armv6/sources.list.buster new file mode 100644 index 000000000..f27fc4fb3 --- /dev/null +++ b/eng/common/cross/armv6/sources.list.buster @@ -0,0 +1,2 @@ +deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi +deb-src http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh new file mode 100644 index 000000000..f163fb9da --- /dev/null +++ b/eng/common/cross/build-android-rootfs.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +set -e +__NDK_Version=r21 + +usage() +{ + echo "Creates a toolchain and sysroot used for cross-compiling for Android." + echo. + echo "Usage: $0 [BuildArch] [ApiLevel]" + echo. + echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." + echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo. + echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" + echo "by setting the TOOLCHAIN_DIR environment variable" + echo. + echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," + echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." + echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." + exit 1 +} + +__ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h +__BuildArch=arm64 +__AndroidArch=aarch64 +__AndroidToolchain=aarch64-linux-android + +for i in "$@" + do + lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm64) + __BuildArch=arm64 + __AndroidArch=aarch64 + __AndroidToolchain=aarch64-linux-android + ;; + arm) + __BuildArch=arm + __AndroidArch=arm + __AndroidToolchain=arm-linux-androideabi + ;; + *[0-9]) + __ApiLevel=$i + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" + ;; + esac +done + +# Obtain the location of the bash script to figure out where the root of the repo is. +__ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +__CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" + +if [[ ! -f "$__CrossDir" ]]; then + mkdir -p "$__CrossDir" +fi + +# Resolve absolute path to avoid `../` in build logs +__CrossDir="$( cd "$__CrossDir" && pwd )" + +__NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" +__lldb_Dir="$__CrossDir/lldb" +__ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" + +if [[ -n "$TOOLCHAIN_DIR" ]]; then + __ToolchainDir=$TOOLCHAIN_DIR +fi + +if [[ -n "$NDK_DIR" ]]; then + __NDK_Dir=$NDK_DIR +fi + +echo "Target API level: $__ApiLevel" +echo "Target architecture: $__BuildArch" +echo "NDK location: $__NDK_Dir" +echo "Target Toolchain location: $__ToolchainDir" + +# Download the NDK if required +if [ ! -d $__NDK_Dir ]; then + echo Downloading the NDK into $__NDK_Dir + mkdir -p $__NDK_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir +fi + +if [ ! -d $__lldb_Dir ]; then + mkdir -p $__lldb_Dir + echo Downloading LLDB into $__lldb_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip + unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir +fi + +echo "Download dependencies..." +__TmpDir=$__CrossDir/tmp/$__BuildArch/ +mkdir -p "$__TmpDir" + +# combined dependencies for coreclr, installer and libraries +__AndroidPackages="libicu" +__AndroidPackages+=" libandroid-glob" +__AndroidPackages+=" liblzma" +__AndroidPackages+=" krb5" +__AndroidPackages+=" openssl" + +for path in $(wget -qO- https://packages.termux.dev/termux-main-21/dists/stable/main/binary-$__AndroidArch/Packages |\ + grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do + + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- https://packages.termux.dev/termux-main-21/$path | dpkg -x - "$__TmpDir" + fi +done + +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" + +# Generate platform file for build.sh script to assign to __DistroRid +echo "Generating platform file..." +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform + +echo "Now to build coreclr, libraries and installers; run:" +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory coreclr +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory libraries +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory installer diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh new file mode 100644 index 000000000..9caf9b021 --- /dev/null +++ b/eng/common/cross/build-rootfs.sh @@ -0,0 +1,648 @@ +#!/usr/bin/env bash + +set -e + +usage() +{ + echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" + echo "BuildArch can be: arm(default), arm64, armel, armv6, ppc64le, riscv64, s390x, x64, x86" + echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" + echo " for alpine can be specified with version: alpineX.YY or alpineedge" + echo " for FreeBSD can be: freebsd12, freebsd13" + echo " for illumos can be: illumos" + echo " for Haiku can be: haiku." + echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" + echo "llvmx[.y] - optional, LLVM version for LLVM related packages." + echo "--skipunmount - optional, will skip the unmount of rootfs folder." + echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." + echo "--use-mirror - optional, use mirror URL to fetch resources, when available." + echo "--jobs N - optional, restrict to N jobs." + exit 1 +} + +__CodeName=xenial +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__BuildArch=arm +__AlpineArch=armv7 +__FreeBSDArch=arm +__FreeBSDMachineArch=armv7 +__IllumosArch=arm7 +__HaikuArch=arm +__QEMUArch=arm +__UbuntuArch=armhf +__UbuntuRepo="http://ports.ubuntu.com/" +__LLDB_Package="liblldb-3.9-dev" +__SkipUnmount=0 + +# base development support +__UbuntuPackages="build-essential" + +__AlpinePackages="alpine-base" +__AlpinePackages+=" build-base" +__AlpinePackages+=" linux-headers" +__AlpinePackages+=" lldb-dev" +__AlpinePackages+=" python3" +__AlpinePackages+=" libedit" + +# symlinks fixer +__UbuntuPackages+=" symlinks" + +# runtime dependencies +__UbuntuPackages+=" libicu-dev" +__UbuntuPackages+=" liblttng-ust-dev" +__UbuntuPackages+=" libunwind8-dev" +__UbuntuPackages+=" libnuma-dev" + +__AlpinePackages+=" gettext-dev" +__AlpinePackages+=" icu-dev" +__AlpinePackages+=" libunwind-dev" +__AlpinePackages+=" lttng-ust-dev" +__AlpinePackages+=" compiler-rt" +__AlpinePackages+=" numactl-dev" + +# runtime libraries' dependencies +__UbuntuPackages+=" libcurl4-openssl-dev" +__UbuntuPackages+=" libkrb5-dev" +__UbuntuPackages+=" libssl-dev" +__UbuntuPackages+=" zlib1g-dev" + +__AlpinePackages+=" curl-dev" +__AlpinePackages+=" krb5-dev" +__AlpinePackages+=" openssl-dev" +__AlpinePackages+=" zlib-dev" + +__FreeBSDBase="12.4-RELEASE" +__FreeBSDPkg="1.17.0" +__FreeBSDABI="12" +__FreeBSDPackages="libunwind" +__FreeBSDPackages+=" icu" +__FreeBSDPackages+=" libinotify" +__FreeBSDPackages+=" openssl" +__FreeBSDPackages+=" krb5" +__FreeBSDPackages+=" terminfo-db" + +__IllumosPackages="icu" +__IllumosPackages+=" mit-krb5" +__IllumosPackages+=" openssl" +__IllumosPackages+=" zlib" + +__HaikuPackages="gcc_syslibs" +__HaikuPackages+=" gcc_syslibs_devel" +__HaikuPackages+=" gmp" +__HaikuPackages+=" gmp_devel" +__HaikuPackages+=" icu66" +__HaikuPackages+=" icu66_devel" +__HaikuPackages+=" krb5" +__HaikuPackages+=" krb5_devel" +__HaikuPackages+=" libiconv" +__HaikuPackages+=" libiconv_devel" +__HaikuPackages+=" llvm12_libunwind" +__HaikuPackages+=" llvm12_libunwind_devel" +__HaikuPackages+=" mpfr" +__HaikuPackages+=" mpfr_devel" +__HaikuPackages+=" openssl" +__HaikuPackages+=" openssl_devel" +__HaikuPackages+=" zlib" +__HaikuPackages+=" zlib_devel" + +# ML.NET dependencies +__UbuntuPackages+=" libomp5" +__UbuntuPackages+=" libomp-dev" + +# Taken from https://github.com/alpinelinux/alpine-chroot-install/blob/6d08f12a8a70dd9b9dc7d997c88aa7789cc03c42/alpine-chroot-install#L85-L133 +__AlpineKeys=' +4a6a0840:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe\nqxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O\nQ0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA\njixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R\nL5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo\nGuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B\nywIDAQAB +5243ef4b:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNijDxJ8kloskKQpJdx+\nmTMVFFUGDoDCbulnhZMJoKNkSuZOzBoFC94omYPtxnIcBdWBGnrm6ncbKRlR+6oy\nDO0W7c44uHKCFGFqBhDasdI4RCYP+fcIX/lyMh6MLbOxqS22TwSLhCVjTyJeeH7K\naA7vqk+QSsF4TGbYzQDDpg7+6aAcNzg6InNePaywA6hbT0JXbxnDWsB+2/LLSF2G\nmnhJlJrWB1WGjkz23ONIWk85W4S0XB/ewDefd4Ly/zyIciastA7Zqnh7p3Ody6Q0\nsS2MJzo7p3os1smGjUF158s6m/JbVh4DN6YIsxwl2OjDOz9R0OycfJSDaBVIGZzg\ncQIDAQAB +524d27bb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr8s1q88XpuJWLCZALdKj\nlN8wg2ePB2T9aIcaxryYE/Jkmtu+ZQ5zKq6BT3y/udt5jAsMrhHTwroOjIsF9DeG\ne8Y3vjz+Hh4L8a7hZDaw8jy3CPag47L7nsZFwQOIo2Cl1SnzUc6/owoyjRU7ab0p\niWG5HK8IfiybRbZxnEbNAfT4R53hyI6z5FhyXGS2Ld8zCoU/R4E1P0CUuXKEN4p0\n64dyeUoOLXEWHjgKiU1mElIQj3k/IF02W89gDj285YgwqA49deLUM7QOd53QLnx+\nxrIrPv3A+eyXMFgexNwCKQU9ZdmWa00MjjHlegSGK8Y2NPnRoXhzqSP9T9i2HiXL\nVQIDAQAB +5261cecb:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlzMkl7b5PBdfMzGdCT0\ncGloRr5xGgVmsdq5EtJvFkFAiN8Ac9MCFy/vAFmS8/7ZaGOXoCDWbYVLTLOO2qtX\nyHRl+7fJVh2N6qrDDFPmdgCi8NaE+3rITWXGrrQ1spJ0B6HIzTDNEjRKnD4xyg4j\ng01FMcJTU6E+V2JBY45CKN9dWr1JDM/nei/Pf0byBJlMp/mSSfjodykmz4Oe13xB\nCa1WTwgFykKYthoLGYrmo+LKIGpMoeEbY1kuUe04UiDe47l6Oggwnl+8XD1MeRWY\nsWgj8sF4dTcSfCMavK4zHRFFQbGp/YFJ/Ww6U9lA3Vq0wyEI6MCMQnoSMFwrbgZw\nwwIDAQAB +58199dcc:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3v8/ye/V/t5xf4JiXLXa\nhWFRozsnmn3hobON20GdmkrzKzO/eUqPOKTpg2GtvBhK30fu5oY5uN2ORiv2Y2ht\neLiZ9HVz3XP8Fm9frha60B7KNu66FO5P2o3i+E+DWTPqqPcCG6t4Znk2BypILcit\nwiPKTsgbBQR2qo/cO01eLLdt6oOzAaF94NH0656kvRewdo6HG4urbO46tCAizvCR\nCA7KGFMyad8WdKkTjxh8YLDLoOCtoZmXmQAiwfRe9pKXRH/XXGop8SYptLqyVVQ+\ntegOD9wRs2tOlgcLx4F/uMzHN7uoho6okBPiifRX+Pf38Vx+ozXh056tjmdZkCaV\naQIDAQAB +58cbb476:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoSPnuAGKtRIS5fEgYPXD\n8pSGvKAmIv3A08LBViDUe+YwhilSHbYXUEAcSH1KZvOo1WT1x2FNEPBEFEFU1Eyc\n+qGzbA03UFgBNvArurHQ5Z/GngGqE7IarSQFSoqewYRtFSfp+TL9CUNBvM0rT7vz\n2eMu3/wWG+CBmb92lkmyWwC1WSWFKO3x8w+Br2IFWvAZqHRt8oiG5QtYvcZL6jym\nY8T6sgdDlj+Y+wWaLHs9Fc+7vBuyK9C4O1ORdMPW15qVSl4Lc2Wu1QVwRiKnmA+c\nDsH/m7kDNRHM7TjWnuj+nrBOKAHzYquiu5iB3Qmx+0gwnrSVf27Arc3ozUmmJbLj\nzQIDAQAB +58e4f17d:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBxJN9ErBgdRcPr5g4hV\nqyUSGZEKuvQliq2Z9SRHLh2J43+EdB6A+yzVvLnzcHVpBJ+BZ9RV30EM9guck9sh\nr+bryZcRHyjG2wiIEoduxF2a8KeWeQH7QlpwGhuobo1+gA8L0AGImiA6UP3LOirl\nI0G2+iaKZowME8/tydww4jx5vG132JCOScMjTalRsYZYJcjFbebQQolpqRaGB4iG\nWqhytWQGWuKiB1A22wjmIYf3t96l1Mp+FmM2URPxD1gk/BIBnX7ew+2gWppXOK9j\n1BJpo0/HaX5XoZ/uMqISAAtgHZAqq+g3IUPouxTphgYQRTRYpz2COw3NF43VYQrR\nbQIDAQAB +60ac2099:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR4uJVtJOnOFGchnMW5Y\nj5/waBdG1u5BTMlH+iQMcV5+VgWhmpZHJCBz3ocD+0IGk2I68S5TDOHec/GSC0lv\n6R9o6F7h429GmgPgVKQsc8mPTPtbjJMuLLs4xKc+viCplXc0Nc0ZoHmCH4da6fCV\ntdpHQjVe6F9zjdquZ4RjV6R6JTiN9v924dGMAkbW/xXmamtz51FzondKC52Gh8Mo\n/oA0/T0KsCMCi7tb4QNQUYrf+Xcha9uus4ww1kWNZyfXJB87a2kORLiWMfs2IBBJ\nTmZ2Fnk0JnHDb8Oknxd9PvJPT0mvyT8DA+KIAPqNvOjUXP4bnjEHJcoCP9S5HkGC\nIQIDAQAB +6165ee59:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54\nALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+\ntFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK\ntlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc\n3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5\nHd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj\nv7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD\nhQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4\nLxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl\nk9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI\nisbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck\nhtBqojBnThmjJQFgZXocHG8CAwEAAQ== +61666e3f:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlEyxkHggKCXC2Wf5Mzx4\nnZLFZvU2bgcA3exfNPO/g1YunKfQY+Jg4fr6tJUUTZ3XZUrhmLNWvpvSwDS19ZmC\nIXOu0+V94aNgnhMsk9rr59I8qcbsQGIBoHzuAl8NzZCgdbEXkiY90w1skUw8J57z\nqCsMBydAueMXuWqF5nGtYbi5vHwK42PffpiZ7G5Kjwn8nYMW5IZdL6ZnMEVJUWC9\nI4waeKg0yskczYDmZUEAtrn3laX9677ToCpiKrvmZYjlGl0BaGp3cxggP2xaDbUq\nqfFxWNgvUAb3pXD09JM6Mt6HSIJaFc9vQbrKB9KT515y763j5CC2KUsilszKi3mB\nHYe5PoebdjS7D1Oh+tRqfegU2IImzSwW3iwA7PJvefFuc/kNIijfS/gH/cAqAK6z\nbhdOtE/zc7TtqW2Wn5Y03jIZdtm12CxSxwgtCF1NPyEWyIxAQUX9ACb3M0FAZ61n\nfpPrvwTaIIxxZ01L3IzPLpbc44x/DhJIEU+iDt6IMTrHOphD9MCG4631eIdB0H1b\n6zbNX1CXTsafqHRFV9XmYYIeOMggmd90s3xIbEujA6HKNP/gwzO6CDJ+nHFDEqoF\nSkxRdTkEqjTjVKieURW7Swv7zpfu5PrsrrkyGnsRrBJJzXlm2FOOxnbI2iSL1B5F\nrO5kbUxFeZUIDq+7Yv4kLWcCAwEAAQ== +616a9724:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnC+bR4bHf/L6QdU4puhQ\ngl1MHePszRC38bzvVFDUJsmCaMCL2suCs2A2yxAgGb9pu9AJYLAmxQC4mM3jNqhg\n/E7yuaBbek3O02zN/ctvflJ250wZCy+z0ZGIp1ak6pu1j14IwHokl9j36zNfGtfv\nADVOcdpWITFFlPqwq1qt/H3UsKVmtiF3BNWWTeUEQwKvlU8ymxgS99yn0+4OPyNT\nL3EUeS+NQJtDS01unau0t7LnjUXn+XIneWny8bIYOQCuVR6s/gpIGuhBaUqwaJOw\n7jkJZYF2Ij7uPb4b5/R3vX2FfxxqEHqssFSg8FFUNTZz3qNZs0CRVyfA972g9WkJ\nhPfn31pQYil4QGRibCMIeU27YAEjXoqfJKEPh4UWMQsQLrEfdGfb8VgwrPbniGfU\nL3jKJR3VAafL9330iawzVQDlIlwGl6u77gEXMl9K0pfazunYhAp+BMP+9ot5ckK+\nosmrqj11qMESsAj083GeFdfV3pXEIwUytaB0AKEht9DbqUfiE/oeZ/LAXgySMtVC\nsbC4ESmgVeY2xSBIJdDyUap7FR49GGrw0W49NUv9gRgQtGGaNVQQO9oGL2PBC41P\niWF9GLoX30HIz1P8PF/cZvicSSPkQf2Z6TV+t0ebdGNS5DjapdnCrq8m9Z0pyKsQ\nuxAL2a7zX8l5i1CZh1ycUGsCAwEAAQ== +616abc23:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0MfCDrhODRCIxR9Dep1s\neXafh5CE5BrF4WbCgCsevyPIdvTeyIaW4vmO3bbG4VzhogDZju+R3IQYFuhoXP5v\nY+zYJGnwrgz3r5wYAvPnLEs1+dtDKYOgJXQj+wLJBW1mzRDL8FoRXOe5iRmn1EFS\nwZ1DoUvyu7/J5r0itKicZp3QKED6YoilXed+1vnS4Sk0mzN4smuMR9eO1mMCqNp9\n9KTfRDHTbakIHwasECCXCp50uXdoW6ig/xUAFanpm9LtK6jctNDbXDhQmgvAaLXZ\nLvFqoaYJ/CvWkyYCgL6qxvMvVmPoRv7OPcyni4xR/WgWa0MSaEWjgPx3+yj9fiMA\n1S02pFWFDOr5OUF/O4YhFJvUCOtVsUPPfA/Lj6faL0h5QI9mQhy5Zb9TTaS9jB6p\nLw7u0dJlrjFedk8KTJdFCcaGYHP6kNPnOxMylcB/5WcztXZVQD5WpCicGNBxCGMm\nW64SgrV7M07gQfL/32QLsdqPUf0i8hoVD8wfQ3EpbQzv6Fk1Cn90bZqZafg8XWGY\nwddhkXk7egrr23Djv37V2okjzdqoyLBYBxMz63qQzFoAVv5VoY2NDTbXYUYytOvG\nGJ1afYDRVWrExCech1mX5ZVUB1br6WM+psFLJFoBFl6mDmiYt0vMYBddKISsvwLl\nIJQkzDwtXzT2cSjoj3T5QekCAwEAAQ== +616ac3bc:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvaaoSLab+IluixwKV5Od\n0gib2YurjPatGIbn5Ov2DLUFYiebj2oJINXJSwUOO+4WcuHFEqiL/1rya+k5hLZt\nhnPL1tn6QD4rESznvGSasRCQNT2vS/oyZbTYJRyAtFkEYLlq0t3S3xBxxHWuvIf0\nqVxVNYpQWyM3N9RIeYBR/euXKJXileSHk/uq1I5wTC0XBIHWcthczGN0m9wBEiWS\n0m3cnPk4q0Ea8mUJ91Rqob19qETz6VbSPYYpZk3qOycjKosuwcuzoMpwU8KRiMFd\n5LHtX0Hx85ghGsWDVtS0c0+aJa4lOMGvJCAOvDfqvODv7gKlCXUpgumGpLdTmaZ8\n1RwqspAe3IqBcdKTqRD4m2mSg23nVx2FAY3cjFvZQtfooT7q1ItRV5RgH6FhQSl7\n+6YIMJ1Bf8AAlLdRLpg+doOUGcEn+pkDiHFgI8ylH1LKyFKw+eXaAml/7DaWZk1d\ndqggwhXOhc/UUZFQuQQ8A8zpA13PcbC05XxN2hyP93tCEtyynMLVPtrRwDnHxFKa\nqKzs3rMDXPSXRn3ZZTdKH3069ApkEjQdpcwUh+EmJ1Ve/5cdtzT6kKWCjKBFZP/s\n91MlRrX2BTRdHaU5QJkUheUtakwxuHrdah2F94lRmsnQlpPr2YseJu6sIE+Dnx4M\nCfhdVbQL2w54R645nlnohu8CAwEAAQ== +616adfeb:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq0BFD1D4lIxQcsqEpQzU\npNCYM3aP1V/fxxVdT4DWvSI53JHTwHQamKdMWtEXetWVbP5zSROniYKFXd/xrD9X\n0jiGHey3lEtylXRIPxe5s+wXoCmNLcJVnvTcDtwx/ne2NLHxp76lyc25At+6RgE6\nADjLVuoD7M4IFDkAsd8UQ8zM0Dww9SylIk/wgV3ZkifecvgUQRagrNUdUjR56EBZ\nraQrev4hhzOgwelT0kXCu3snbUuNY/lU53CoTzfBJ5UfEJ5pMw1ij6X0r5S9IVsy\nKLWH1hiO0NzU2c8ViUYCly4Fe9xMTFc6u2dy/dxf6FwERfGzETQxqZvSfrRX+GLj\n/QZAXiPg5178hT/m0Y3z5IGenIC/80Z9NCi+byF1WuJlzKjDcF/TU72zk0+PNM/H\nKuppf3JT4DyjiVzNC5YoWJT2QRMS9KLP5iKCSThwVceEEg5HfhQBRT9M6KIcFLSs\nmFjx9kNEEmc1E8hl5IR3+3Ry8G5/bTIIruz14jgeY9u5jhL8Vyyvo41jgt9sLHR1\n/J1TxKfkgksYev7PoX6/ZzJ1ksWKZY5NFoDXTNYUgzFUTOoEaOg3BAQKadb3Qbbq\nXIrxmPBdgrn9QI7NCgfnAY3Tb4EEjs3ON/BNyEhUENcXOH6I1NbcuBQ7g9P73kE4\nVORdoc8MdJ5eoKBpO8Ww8HECAwEAAQ== +616ae350:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyduVzi1mWm+lYo2Tqt/0\nXkCIWrDNP1QBMVPrE0/ZlU2bCGSoo2Z9FHQKz/mTyMRlhNqTfhJ5qU3U9XlyGOPJ\npiM+b91g26pnpXJ2Q2kOypSgOMOPA4cQ42PkHBEqhuzssfj9t7x47ppS94bboh46\nxLSDRff/NAbtwTpvhStV3URYkxFG++cKGGa5MPXBrxIp+iZf9GnuxVdST5PGiVGP\nODL/b69sPJQNbJHVquqUTOh5Ry8uuD2WZuXfKf7/C0jC/ie9m2+0CttNu9tMciGM\nEyKG1/Xhk5iIWO43m4SrrT2WkFlcZ1z2JSf9Pjm4C2+HovYpihwwdM/OdP8Xmsnr\nDzVB4YvQiW+IHBjStHVuyiZWc+JsgEPJzisNY0Wyc/kNyNtqVKpX6dRhMLanLmy+\nf53cCSI05KPQAcGj6tdL+D60uKDkt+FsDa0BTAobZ31OsFVid0vCXtsbplNhW1IF\nHwsGXBTVcfXg44RLyL8Lk/2dQxDHNHzAUslJXzPxaHBLmt++2COa2EI1iWlvtznk\nOk9WP8SOAIj+xdqoiHcC4j72BOVVgiITIJNHrbppZCq6qPR+fgXmXa+sDcGh30m6\n9Wpbr28kLMSHiENCWTdsFij+NQTd5S47H7XTROHnalYDuF1RpS+DpQidT5tUimaT\nJZDr++FjKrnnijbyNF8b98UCAwEAAQ== +616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== +' +__Keyring= +__SkipSigCheck=0 +__UseMirror=0 + +__UnprocessedBuildArgs= +while :; do + if [[ "$#" -le 0 ]]; then + break + fi + + lowerI="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -\?|-h|--help) + usage + exit 1 + ;; + arm) + __BuildArch=arm + __UbuntuArch=armhf + __AlpineArch=armv7 + __QEMUArch=arm + ;; + arm64) + __BuildArch=arm64 + __UbuntuArch=arm64 + __AlpineArch=aarch64 + __QEMUArch=aarch64 + __FreeBSDArch=arm64 + __FreeBSDMachineArch=aarch64 + ;; + armel) + __BuildArch=armel + __UbuntuArch=armel + __UbuntuRepo="http://ftp.debian.org/debian/" + __CodeName=jessie + ;; + armv6) + __BuildArch=armv6 + __UbuntuArch=armhf + __QEMUArch=arm + __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" + __CodeName=buster + __LLDB_Package="liblldb-6.0-dev" + + if [[ -e "/usr/share/keyrings/raspbian-archive-keyring.gpg" ]]; then + __Keyring="--keyring /usr/share/keyrings/raspbian-archive-keyring.gpg" + fi + ;; + riscv64) + __BuildArch=riscv64 + __AlpineArch=riscv64 + __AlpinePackages="${__AlpinePackages// lldb-dev/}" + __QEMUArch=riscv64 + __UbuntuArch=riscv64 + __UbuntuRepo="http://deb.debian.org/debian-ports" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + unset __LLDB_Package + + if [[ -e "/usr/share/keyrings/debian-ports-archive-keyring.gpg" ]]; then + __Keyring="--keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --include=debian-ports-archive-keyring" + fi + ;; + ppc64le) + __BuildArch=ppc64le + __AlpineArch=ppc64le + __QEMUArch=ppc64le + __UbuntuArch=ppc64el + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + unset __LLDB_Package + ;; + s390x) + __BuildArch=s390x + __AlpineArch=s390x + __QEMUArch=s390x + __UbuntuArch=s390x + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" + __UbuntuPackages="${__UbuntuPackages// libomp5/}" + unset __LLDB_Package + ;; + x64) + __BuildArch=x64 + __AlpineArch=x86_64 + __UbuntuArch=amd64 + __FreeBSDArch=amd64 + __FreeBSDMachineArch=amd64 + __illumosArch=x86_64 + __HaikuArch=x86_64 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + x86) + __BuildArch=x86 + __UbuntuArch=i386 + __AlpineArch=x86 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + lldb*) + version="${lowerI/lldb/}" + parts=(${version//./ }) + + # for versions > 6.0, lldb has dropped the minor version + if [[ "${parts[0]}" -gt 6 ]]; then + version="${parts[0]}" + fi + + __LLDB_Package="liblldb-${version}-dev" + ;; + no-lldb) + unset __LLDB_Package + ;; + llvm*) + version="${lowerI/llvm/}" + parts=(${version//./ }) + __LLVM_MajorVersion="${parts[0]}" + __LLVM_MinorVersion="${parts[1]}" + + # for versions > 6.0, llvm has dropped the minor version + if [[ -z "$__LLVM_MinorVersion" && "$__LLVM_MajorVersion" -le 6 ]]; then + __LLVM_MinorVersion=0; + fi + ;; + xenial) # Ubuntu 16.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=xenial + fi + ;; + zesty) # Ubuntu 17.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=zesty + fi + ;; + bionic) # Ubuntu 18.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=bionic + fi + ;; + focal) # Ubuntu 20.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=focal + fi + ;; + jammy) # Ubuntu 22.04 + if [[ "$__CodeName" != "jessie" ]]; then + __CodeName=jammy + fi + ;; + jessie) # Debian 8 + __CodeName=jessie + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + stretch) # Debian 9 + __CodeName=stretch + __LLDB_Package="liblldb-6.0-dev" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + buster) # Debian 10 + __CodeName=buster + __LLDB_Package="liblldb-6.0-dev" + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + bullseye) # Debian 11 + __CodeName=bullseye + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + sid) # Debian sid + __CodeName=sid + + if [[ -z "$__UbuntuRepo" ]]; then + __UbuntuRepo="http://ftp.debian.org/debian/" + fi + ;; + tizen) + __CodeName= + __UbuntuRepo= + __Tizen=tizen + ;; + alpine*) + __CodeName=alpine + __UbuntuRepo= + version="${lowerI/alpine/}" + + if [[ "$version" == "edge" ]]; then + __AlpineVersion=edge + else + parts=(${version//./ }) + __AlpineMajorVersion="${parts[0]}" + __AlpineMinoVersion="${parts[1]}" + __AlpineVersion="$__AlpineMajorVersion.$__AlpineMinoVersion" + fi + ;; + freebsd12) + __CodeName=freebsd + __SkipUnmount=1 + ;; + freebsd13) + __CodeName=freebsd + __FreeBSDBase="13.2-RELEASE" + __FreeBSDABI="13" + __SkipUnmount=1 + ;; + illumos) + __CodeName=illumos + __SkipUnmount=1 + ;; + haiku) + __CodeName=haiku + __SkipUnmount=1 + ;; + --skipunmount) + __SkipUnmount=1 + ;; + --skipsigcheck) + __SkipSigCheck=1 + ;; + --rootfsdir|-rootfsdir) + shift + __RootfsDir="$1" + ;; + --use-mirror) + __UseMirror=1 + ;; + --use-jobs) + shift + MAXJOBS=$1 + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" + ;; + esac + + shift +done + +case "$__AlpineVersion" in + 3.14) __AlpinePackages+=" llvm11-libs" ;; + 3.15) __AlpinePackages+=" llvm12-libs" ;; + 3.16) __AlpinePackages+=" llvm13-libs" ;; + 3.17) __AlpinePackages+=" llvm15-libs" ;; + edge) __AlpineLlvmLibsLookup=1 ;; + *) + if [[ "$__AlpineArch" =~ s390x|ppc64le ]]; then + __AlpineVersion=3.15 # minimum version that supports lldb-dev + __AlpinePackages+=" llvm12-libs" + elif [[ "$__AlpineArch" == "x86" ]]; then + __AlpineVersion=3.17 # minimum version that supports lldb-dev + __AlpinePackages+=" llvm15-libs" + elif [[ "$__AlpineArch" == "riscv64" ]]; then + __AlpineLlvmLibsLookup=1 + __AlpineVersion=edge # minimum version with APKINDEX.tar.gz (packages archive) + else + __AlpineVersion=3.13 # 3.13 to maximize compatibility + __AlpinePackages+=" llvm10-libs" + + if [[ "$__AlpineArch" == "armv7" ]]; then + __AlpinePackages="${__AlpinePackages//numactl-dev/}" + fi + fi +esac + +if [[ "$__AlpineVersion" =~ 3\.1[345] ]]; then + # compiler-rt--static was merged in compiler-rt package in alpine 3.16 + # for older versions, we need compiler-rt--static, so replace the name + __AlpinePackages="${__AlpinePackages/compiler-rt/compiler-rt-static}" +fi + +if [[ "$__BuildArch" == "armel" ]]; then + __LLDB_Package="lldb-3.5-dev" +fi + +if [[ "$__CodeName" == "xenial" && "$__UbuntuArch" == "armhf" ]]; then + # libnuma-dev is not available on armhf for xenial + __UbuntuPackages="${__UbuntuPackages//libnuma-dev/}" +fi + +__UbuntuPackages+=" ${__LLDB_Package:-}" + +if [[ -n "$__LLVM_MajorVersion" ]]; then + __UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev" +fi + +if [[ -z "$__RootfsDir" && -n "$ROOTFS_DIR" ]]; then + __RootfsDir="$ROOTFS_DIR" +fi + +if [[ -z "$__RootfsDir" ]]; then + __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" +fi + +if [[ -d "$__RootfsDir" ]]; then + if [[ "$__SkipUnmount" == "0" ]]; then + umount "$__RootfsDir"/* || true + fi + rm -rf "$__RootfsDir" +fi + +mkdir -p "$__RootfsDir" +__RootfsDir="$( cd "$__RootfsDir" && pwd )" + +if [[ "$__CodeName" == "alpine" ]]; then + __ApkToolsVersion=2.12.11 + __ApkToolsSHA512SUM=53e57b49230da07ef44ee0765b9592580308c407a8d4da7125550957bb72cb59638e04f8892a18b584451c8d841d1c7cb0f0ab680cc323a3015776affaa3be33 + __ApkToolsDir="$(mktemp -d)" + __ApkKeysDir="$(mktemp -d)" + + wget "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic//v$__ApkToolsVersion/x86_64/apk.static" -P "$__ApkToolsDir" + echo "$__ApkToolsSHA512SUM $__ApkToolsDir/apk.static" | sha512sum -c + chmod +x "$__ApkToolsDir/apk.static" + + if [[ -f "/usr/bin/qemu-$__QEMUArch-static" ]]; then + mkdir -p "$__RootfsDir"/usr/bin + cp -v "/usr/bin/qemu-$__QEMUArch-static" "$__RootfsDir/usr/bin" + fi + + if [[ "$__AlpineVersion" == "edge" ]]; then + version=edge + else + version="v$__AlpineVersion" + fi + + for line in $__AlpineKeys; do + id="${line%%:*}" + content="${line#*:}" + + echo -e "-----BEGIN PUBLIC KEY-----\n$content\n-----END PUBLIC KEY-----" > "$__ApkKeysDir/alpine-devel@lists.alpinelinux.org-$id.rsa.pub" + done + + if [[ "$__SkipSigCheck" == "1" ]]; then + __ApkSignatureArg="--allow-untrusted" + else + __ApkSignatureArg="--keys-dir $__ApkKeysDir" + fi + + # initialize DB + "$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add + + if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then + __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ + search 'llvm*-libs' | sort | tail -1 | sed 's/-[^-]*//2g')" + fi + + # install all packages in one go + "$__ApkToolsDir/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ + add $__AlpinePackages + + rm -r "$__ApkToolsDir" +elif [[ "$__CodeName" == "freebsd" ]]; then + mkdir -p "$__RootfsDir"/usr/local/etc + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + wget -O - "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version + echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf + echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"${__RootfsDir}/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf + mkdir -p "$__RootfsDir"/tmp + # get and build package manager + wget -O - "https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz" | tar -C "$__RootfsDir"/tmp -zxf - + cd "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" + # needed for install to succeed + mkdir -p "$__RootfsDir"/host/etc + ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install + rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" + # install packages we need. + INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "illumos" ]]; then + mkdir "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + echo "Downloading sysroot." + wget -O - https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - + echo "Building binutils. Please wait.." + wget -O - https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.bz2 | tar -xjf - + mkdir build-binutils && cd build-binutils + ../binutils-2.33.1/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.10" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" + make -j "$JOBS" && make install && cd .. + echo "Building gcc. Please wait.." + wget -O - https://ftp.gnu.org/gnu/gcc/gcc-8.4.0/gcc-8.4.0.tar.xz | tar -xJf - + CFLAGS="-fPIC" + CXXFLAGS="-fPIC" + CXXFLAGS_FOR_TARGET="-fPIC" + CFLAGS_FOR_TARGET="-fPIC" + export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET + mkdir build-gcc && cd build-gcc + ../gcc-8.4.0/configure --prefix="$__RootfsDir" --target="${__illumosArch}-sun-solaris2.10" --program-prefix="${__illumosArch}-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ + --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ + --disable-libquadmath-support --disable-shared --enable-tls + make -j "$JOBS" && make install && cd .. + BaseUrl=https://pkgsrc.smartos.org + if [[ "$__UseMirror" == 1 ]]; then + BaseUrl=https://pkgsrc.smartos.skylime.net + fi + BaseUrl="$BaseUrl/packages/SmartOS/trunk/${__illumosArch}/All" + echo "Downloading manifest" + wget "$BaseUrl" + echo "Downloading dependencies." + read -ra array <<<"$__IllumosPackages" + for package in "${array[@]}"; do + echo "Installing '$package'" + # find last occurrence of package in listing and extract its name + package="$(sed -En '/.*href="('"$package"'-[0-9].*).tgz".*/h;$!d;g;s//\1/p' All)" + echo "Resolved name '$package'" + wget "$BaseUrl"/"$package".tgz + ar -x "$package".tgz + tar --skip-old-files -xzf "$package".tmp.tg* -C "$__RootfsDir" 2>/dev/null + done + echo "Cleaning up temporary files." + popd + rm -rf "$__RootfsDir"/{tmp,+*} + mkdir -p "$__RootfsDir"/usr/include/net + mkdir -p "$__RootfsDir"/usr/include/netpacket + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h + wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h + wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h +elif [[ "$__CodeName" == "haiku" ]]; then + JOBS=${MAXJOBS:="$(getconf _NPROCESSORS_ONLN)"} + + echo "Building Haiku sysroot for $__HaikuArch" + mkdir -p "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + + mkdir "$__RootfsDir/tmp/download" + + echo "Downloading Haiku package tool" + git clone https://github.com/haiku/haiku-toolchains-ubuntu --depth 1 $__RootfsDir/tmp/script + wget -O "$__RootfsDir/tmp/download/hosttools.zip" $($__RootfsDir/tmp/script/fetch.sh --hosttools) + unzip -o "$__RootfsDir/tmp/download/hosttools.zip" -d "$__RootfsDir/tmp/bin" + + DepotBaseUrl="https://depot.haiku-os.org/__api/v2/pkg/get-pkg" + HpkgBaseUrl="https://eu.hpkg.haiku-os.org/haiku/master/$__HaikuArch/current" + + # Download Haiku packages + echo "Downloading Haiku packages" + read -ra array <<<"$__HaikuPackages" + for package in "${array[@]}"; do + echo "Downloading $package..." + # API documented here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L60 + # The schema here: https://github.com/haiku/haikudepotserver/blob/master/haikudepotserver-api2/src/main/resources/api2/pkg.yaml#L598 + hpkgDownloadUrl="$(wget -qO- --post-data='{"name":"'"$package"'","repositorySourceCode":"haikuports_'$__HaikuArch'","versionType":"LATEST","naturalLanguageCode":"en"}' \ + --header='Content-Type:application/json' "$DepotBaseUrl" | jq -r '.result.versions[].hpkgDownloadURL')" + wget -P "$__RootfsDir/tmp/download" "$hpkgDownloadUrl" + done + for package in haiku haiku_devel; do + echo "Downloading $package..." + hpkgVersion="$(wget -qO- $HpkgBaseUrl | sed -n 's/^.*version: "\([^"]*\)".*$/\1/p')" + wget -P "$__RootfsDir/tmp/download" "$HpkgBaseUrl/packages/$package-$hpkgVersion-1-$__HaikuArch.hpkg" + done + + # Set up the sysroot + echo "Setting up sysroot and extracting required packages" + mkdir -p "$__RootfsDir/boot/system" + for file in "$__RootfsDir/tmp/download/"*.hpkg; do + echo "Extracting $file..." + LD_LIBRARY_PATH="$__RootfsDir/tmp/bin" "$__RootfsDir/tmp/bin/package" extract -C "$__RootfsDir/boot/system" "$file" + done + + # Download buildtools + echo "Downloading Haiku buildtools" + wget -O "$__RootfsDir/tmp/download/buildtools.zip" $($__RootfsDir/tmp/script/fetch.sh --buildtools --arch=$__HaikuArch) + unzip -o "$__RootfsDir/tmp/download/buildtools.zip" -d "$__RootfsDir" + + # Cleaning up temporary files + echo "Cleaning up temporary files" + popd + rm -rf "$__RootfsDir/tmp" +elif [[ -n "$__CodeName" ]]; then + + if [[ "$__SkipSigCheck" == "0" ]]; then + __Keyring="$__Keyring --force-check-gpg" + fi + + debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" + cp "$__CrossDir/$__BuildArch/sources.list.$__CodeName" "$__RootfsDir/etc/apt/sources.list" + chroot "$__RootfsDir" apt-get update + chroot "$__RootfsDir" apt-get -f -y install + chroot "$__RootfsDir" apt-get -y install $__UbuntuPackages + chroot "$__RootfsDir" symlinks -cr /usr + chroot "$__RootfsDir" apt-get clean + + if [[ "$__SkipUnmount" == "0" ]]; then + umount "$__RootfsDir"/* || true + fi + + if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then + pushd "$__RootfsDir" + patch -p1 < "$__CrossDir/$__BuildArch/armel.jessie.patch" + popd + fi +elif [[ "$__Tizen" == "tizen" ]]; then + ROOTFS_DIR="$__RootfsDir" "$__CrossDir/tizen-build-rootfs.sh" "$__BuildArch" +else + echo "Unsupported target platform." + usage; + exit 1 +fi diff --git a/eng/common/cross/ppc64le/sources.list.bionic b/eng/common/cross/ppc64le/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/ppc64le/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/riscv64/sources.list.sid b/eng/common/cross/riscv64/sources.list.sid new file mode 100644 index 000000000..65f730d22 --- /dev/null +++ b/eng/common/cross/riscv64/sources.list.sid @@ -0,0 +1 @@ +deb http://deb.debian.org/debian-ports sid main diff --git a/eng/common/cross/s390x/sources.list.bionic b/eng/common/cross/s390x/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/s390x/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/tizen-build-rootfs.sh b/eng/common/cross/tizen-build-rootfs.sh new file mode 100644 index 000000000..ac84173d4 --- /dev/null +++ b/eng/common/cross/tizen-build-rootfs.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -e + +ARCH=$1 +LINK_ARCH=$ARCH + +case "$ARCH" in + arm) + TIZEN_ARCH="armv7hl" + ;; + armel) + TIZEN_ARCH="armv7l" + LINK_ARCH="arm" + ;; + arm64) + TIZEN_ARCH="aarch64" + ;; + x86) + TIZEN_ARCH="i686" + ;; + x64) + TIZEN_ARCH="x86_64" + LINK_ARCH="x86" + ;; + *) + echo "Unsupported architecture for tizen: $ARCH" + exit 1 +esac + +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__CrossDir/${ARCH}/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR $TIZEN_ARCH +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +ln -sfn asm-${LINK_ARCH} ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_ARCH=$2 + +TIZEN_URL=http://download.tizen.org/snapshots/TIZEN/Tizen +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize ${TIZEN_ARCH} base" +fetch_tizen_pkgs_init standard Tizen-Base +Inform "fetch common packages" +fetch_tizen_pkgs ${TIZEN_ARCH} gcc gcc-devel-static glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils +Inform "fetch coreclr packages" +fetch_tizen_pkgs ${TIZEN_ARCH} lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs ${TIZEN_ARCH} libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard Tizen-Unified +Inform "fetch corefx packages" +fetch_tizen_pkgs ${TIZEN_ARCH} gssdp gssdp-devel tizen-release + diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake new file mode 100644 index 000000000..f93dc440d --- /dev/null +++ b/eng/common/cross/toolchain.cmake @@ -0,0 +1,380 @@ +set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) + +# reset platform variables (e.g. cmake 3.25 sets LINUX=1) +unset(LINUX) +unset(FREEBSD) +unset(ILLUMOS) +unset(ANDROID) +unset(TIZEN) +unset(HAIKU) + +set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) +if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) + set(CMAKE_SYSTEM_NAME FreeBSD) + set(FREEBSD 1) +elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) + set(CMAKE_SYSTEM_NAME SunOS) + set(ILLUMOS 1) +elseif(EXISTS ${CROSS_ROOTFS}/boot/system/develop/headers/config/HaikuConfig.h) + set(CMAKE_SYSTEM_NAME Haiku) + set(HAIKU 1) +else() + set(CMAKE_SYSTEM_NAME Linux) + set(LINUX 1) +endif() +set(CMAKE_SYSTEM_VERSION 1) + +if(EXISTS ${CROSS_ROOTFS}/etc/tizen-release) + set(TIZEN 1) +elseif(EXISTS ${CROSS_ROOTFS}/android_platform) + set(ANDROID 1) +endif() + +if(TARGET_ARCH_NAME STREQUAL "arm") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) + set(TOOLCHAIN "armv7-alpine-linux-musleabihf") + elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() + if(TIZEN) + set(TIZEN_TOOLCHAIN "armv7hl-tizen-linux-gnueabihf") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(CMAKE_SYSTEM_PROCESSOR aarch64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) + set(TOOLCHAIN "aarch64-alpine-linux-musl") + elseif(LINUX) + set(TOOLCHAIN "aarch64-linux-gnu") + if(TIZEN) + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") + endif() + elseif(FREEBSD) + set(triple "aarch64-unknown-freebsd12") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "armel") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + set(TOOLCHAIN "arm-linux-gnueabi") + if(TIZEN) + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "armv6") + set(CMAKE_SYSTEM_PROCESSOR armv6l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "ppc64le") + set(CMAKE_SYSTEM_PROCESSOR ppc64le) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl) + set(TOOLCHAIN "powerpc64le-alpine-linux-musl") + else() + set(TOOLCHAIN "powerpc64le-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "riscv64") + set(CMAKE_SYSTEM_PROCESSOR riscv64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/riscv64-alpine-linux-musl) + set(TOOLCHAIN "riscv64-alpine-linux-musl") + else() + set(TOOLCHAIN "riscv64-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + set(CMAKE_SYSTEM_PROCESSOR s390x) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/s390x-alpine-linux-musl) + set(TOOLCHAIN "s390x-alpine-linux-musl") + else() + set(TOOLCHAIN "s390x-linux-gnu") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x64") + set(CMAKE_SYSTEM_PROCESSOR x86_64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/x86_64-alpine-linux-musl) + set(TOOLCHAIN "x86_64-alpine-linux-musl") + elseif(LINUX) + set(TOOLCHAIN "x86_64-linux-gnu") + if(TIZEN) + set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") + endif() + elseif(FREEBSD) + set(triple "x86_64-unknown-freebsd12") + elseif(ILLUMOS) + set(TOOLCHAIN "x86_64-illumos") + elseif(HAIKU) + set(TOOLCHAIN "x86_64-unknown-haiku") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + set(CMAKE_SYSTEM_PROCESSOR i686) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + set(TOOLCHAIN "i586-alpine-linux-musl") + else() + set(TOOLCHAIN "i686-linux-gnu") + endif() + if(TIZEN) + set(TIZEN_TOOLCHAIN "i586-tizen-linux-gnu") + endif() +else() + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only arm, arm64, armel, armv6, ppc64le, riscv64, s390x, x64 and x86 are supported!") +endif() + +if(DEFINED ENV{TOOLCHAIN}) + set(TOOLCHAIN $ENV{TOOLCHAIN}) +endif() + +# Specify include paths +if(TIZEN) + function(find_toolchain_dir prefix) + # Dynamically find the version subdirectory + file(GLOB DIRECTORIES "${prefix}/*") + list(GET DIRECTORIES 0 FIRST_MATCH) + get_filename_component(TOOLCHAIN_VERSION ${FIRST_MATCH} NAME) + + set(TIZEN_TOOLCHAIN_PATH "${prefix}/${TOOLCHAIN_VERSION}" PARENT_SCOPE) + endfunction() + + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + else() + find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + endif() + + message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") + + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) + include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) +endif() + +if(ANDROID) + if(TARGET_ARCH_NAME STREQUAL "arm") + set(ANDROID_ABI armeabi-v7a) + elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(ANDROID_ABI arm64-v8a) + endif() + + # extract platform number required by the NDK's toolchain + file(READ "${CROSS_ROOTFS}/android_platform" RID_FILE_CONTENTS) + string(REPLACE "RID=" "" ANDROID_RID "${RID_FILE_CONTENTS}") + string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "${ANDROID_RID}") + + set(ANDROID_TOOLCHAIN clang) + set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository + set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") + set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") + + # include official NDK toolchain script + include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) +elseif(FREEBSD) + # we cross-compile by instructing clang + set(CMAKE_C_COMPILER_TARGET ${triple}) + set(CMAKE_CXX_COMPILER_TARGET ${triple}) + set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld") +elseif(ILLUMOS) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + include_directories(SYSTEM ${CROSS_ROOTFS}/include) + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") +elseif(HAIKU) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") + + # let CMake set up the correct search paths + include(Platform/Haiku) +else() + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") +endif() + +# Specify link flags + +function(add_toolchain_linker_flag Flag) + set(Config "${ARGV1}") + set(CONFIG_SUFFIX "") + if (NOT Config STREQUAL "") + set(CONFIG_SUFFIX "_${Config}") + endif() + set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) + set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}_INIT} ${Flag}" PARENT_SCOPE) +endfunction() + +if(LINUX) + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + if(TIZEN) + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") + endif() +elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64)$") + if(TIZEN) + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") + + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + add_toolchain_linker_flag("--target=${TOOLCHAIN}") +elseif(TARGET_ARCH_NAME STREQUAL "x86") + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + add_toolchain_linker_flag("--target=${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/gcc/${TOOLCHAIN}") + endif() + add_toolchain_linker_flag(-m32) + if(TIZEN) + add_toolchain_linker_flag("-B${TIZEN_TOOLCHAIN_PATH}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${TIZEN_TOOLCHAIN_PATH}") + endif() +elseif(ILLUMOS) + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") +elseif(HAIKU) + add_toolchain_linker_flag("-lnetwork") + add_toolchain_linker_flag("-lroot") +endif() + +# Specify compile options + +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + add_compile_options(-mthumb) + if (NOT DEFINED CLR_ARM_FPU_TYPE) + set (CLR_ARM_FPU_TYPE vfpv3) + endif (NOT DEFINED CLR_ARM_FPU_TYPE) + + add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) + if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + set (CLR_ARM_FPU_CAPABILITY 0x7) + endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + + add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) + + # persist variables across multiple try_compile passes + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CLR_ARM_FPU_TYPE CLR_ARM_FPU_CAPABILITY) + + if(TARGET_ARCH_NAME STREQUAL "armel") + add_compile_options(-mfloat-abi=softfp) + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + add_compile_options("--target=${TOOLCHAIN}") +elseif(TARGET_ARCH_NAME STREQUAL "x86") + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl) + add_compile_options(--target=${TOOLCHAIN}) + endif() + add_compile_options(-m32) + add_compile_options(-Wno-error=unused-command-line-argument) +endif() + +if(TIZEN) + if(TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|x86)$") + add_compile_options(-Wno-deprecated-declarations) # compile-time option + add_compile_options(-D__extern_always_inline=inline) # compile-time option + endif() +endif() + +# Set LLDB include and library paths for builds that need lldb. +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + if(TARGET_ARCH_NAME STREQUAL "x86") + set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") + else() # arm/armel case + set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") + endif() + if(LLVM_CROSS_DIR) + set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") + set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") + set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") + else() + if(TARGET_ARCH_NAME STREQUAL "x86") + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") + set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") + if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") + set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") + else() + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") + endif() + else() # arm/armel case + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") + endif() + endif() +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/x86/sources.list.bionic b/eng/common/cross/x86/sources.list.bionic new file mode 100644 index 000000000..a71ccadcf --- /dev/null +++ b/eng/common/cross/x86/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.xenial b/eng/common/cross/x86/sources.list.xenial new file mode 100644 index 000000000..ad9c5a014 --- /dev/null +++ b/eng/common/cross/x86/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 new file mode 100644 index 000000000..8fda30bdc --- /dev/null +++ b/eng/common/darc-init.ps1 @@ -0,0 +1,47 @@ +param ( + $darcVersion = $null, + $versionEndpoint = 'https://maestro.dot.net/api/assets/darc-version?api-version=2019-01-16', + $verbosity = 'minimal', + $toolpath = $null +) + +. $PSScriptRoot\tools.ps1 + +function InstallDarcCli ($darcVersion, $toolpath) { + $darcCliPackageName = 'microsoft.dotnet.darc' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list -g + + if ($toolList -like "*$darcCliPackageName*") { + & "$dotnet" tool uninstall $darcCliPackageName -g + } + + # If the user didn't explicitly specify the darc version, + # query the Maestro API for the correct version of darc to install. + if (-not $darcVersion) { + $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content + } + + $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + + Write-Host "Installing Darc CLI version $darcVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + if (-not $toolpath) { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + }else { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + } +} + +try { + InstallDarcCli $darcVersion $toolpath +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Darc' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh new file mode 100644 index 000000000..c305ae6bd --- /dev/null +++ b/eng/common/darc-init.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +darcVersion='' +versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2019-01-16' +verbosity='minimal' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --darcversion) + darcVersion=$2 + shift + ;; + --versionendpoint) + versionEndpoint=$2 + shift + ;; + --verbosity) + verbosity=$2 + shift + ;; + --toolpath) + toolpath=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ -z "$darcVersion" ]; then + darcVersion=$(curl -X GET "$versionEndpoint" -H "accept: text/plain") +fi + +function InstallDarcCli { + local darc_cli_package_name="microsoft.dotnet.darc" + + InitializeDotNetCli true + local dotnet_root=$_InitializeDotNetCli + + if [ -z "$toolpath" ]; then + local tool_list=$($dotnet_root/dotnet tool list -g) + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + fi + else + local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") + fi + fi + + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + + echo "Installing Darc CLI version $darcVersion..." + echo "You may need to restart your command shell if this is the first dotnet tool you have installed." + if [ -z "$toolpath" ]; then + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + else + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + fi +} + +InstallDarcCli diff --git a/eng/common/dotnet-install.cmd b/eng/common/dotnet-install.cmd new file mode 100644 index 000000000..b1c2642e7 --- /dev/null +++ b/eng/common/dotnet-install.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" \ No newline at end of file diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 new file mode 100644 index 000000000..811f0f717 --- /dev/null +++ b/eng/common/dotnet-install.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [string] $architecture = '', + [string] $version = 'Latest', + [string] $runtime = 'dotnet', + [string] $RuntimeSourceFeed = '', + [string] $RuntimeSourceFeedKey = '' +) + +. $PSScriptRoot\tools.ps1 + +$dotnetRoot = Join-Path $RepoRoot '.dotnet' + +$installdir = $dotnetRoot +try { + if ($architecture -and $architecture.Trim() -eq 'x86') { + $installdir = Join-Path $installdir 'x86' + } + InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh new file mode 100644 index 000000000..7e69e3a9e --- /dev/null +++ b/eng/common/dotnet-install.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +version='Latest' +architecture='' +runtime='dotnet' +runtimeSourceFeed='' +runtimeSourceFeedKey='' +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -version|-v) + shift + version="$1" + ;; + -architecture|-a) + shift + architecture="$1" + ;; + -runtime|-r) + shift + runtime="$1" + ;; + -runtimesourcefeed) + shift + runtimeSourceFeed="$1" + ;; + -runtimesourcefeedkey) + shift + runtimeSourceFeedKey="$1" + ;; + *) + Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" + exit 1 + ;; + esac + shift +done + +# Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples +cpuname=$(uname -m) +case $cpuname in + arm64|aarch64) + buildarch=arm64 + if [ "$(getconf LONG_BIT)" -lt 64 ]; then + # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) + buildarch=arm + fi + ;; + loongarch64) + buildarch=loongarch64 + ;; + amd64|x86_64) + buildarch=x64 + ;; + armv*l) + buildarch=arm + ;; + i[3-6]86) + buildarch=x86 + ;; + *) + echo "Unknown CPU $cpuname detected, treating it as x64" + buildarch=x64 + ;; +esac + +dotnetRoot="${repo_root}.dotnet" +if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then + dotnetRoot="$dotnetRoot/$architecture" +fi + +InstallDotNet $dotnetRoot $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { + local exit_code=$? + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code +} + +ExitWithExitCode 0 diff --git a/eng/common/enable-cross-org-publishing.ps1 b/eng/common/enable-cross-org-publishing.ps1 new file mode 100644 index 000000000..da09da4f1 --- /dev/null +++ b/eng/common/enable-cross-org-publishing.ps1 @@ -0,0 +1,13 @@ +param( + [string] $token +) + + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Write-PipelineSetVariable will no-op if a variable named $ci is not defined +# Since this script is only ever called in AzDO builds, just universally set it +$ci = $true + +Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false +Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 new file mode 100644 index 000000000..524aaa57f --- /dev/null +++ b/eng/common/generate-locproject.ps1 @@ -0,0 +1,189 @@ +Param( + [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here + [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json + [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one + [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally +) + +# Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: +# https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = "Stop" +. $PSScriptRoot\pipeline-logging-functions.ps1 + +$exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" +$exclusions = @{ Exclusions = @() } +if (Test-Path -Path $exclusionsFilePath) +{ + $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json +} + +Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work + +# Template files +$jsonFiles = @() +$jsonTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\.+\.en\.json" } # .NET templating pattern +$jsonTemplateFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.json" # matches '[filename].[langcode].json + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).json" + $jsonFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$jsonWinformsTemplateFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern + +$wxlFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\.+\.wxl" -And -Not( $_.Directory.Name -Match "\d{4}" ) } # localized files live in four digit lang ID directories; this excludes them +if (-not $wxlFiles) { + $wxlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\\1033\\.+\.wxl" } # pick up en files (1033 = en) specifically so we can copy them to use as the neutral xlf files + if ($wxlEnFiles) { + $wxlFiles = @() + $wxlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $wxlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } + } +} + +$macosHtmlEnFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\.lproj\\.+\.html$" } # add installer HTML files +$macosHtmlFiles = @() +if ($macosHtmlEnFiles) { + $macosHtmlEnFiles | ForEach-Object { + $destinationFile = "$($_.Directory.Parent.FullName)\$($_.Name)" + $macosHtmlFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru + } +} + +$xlfFiles = @() + +$allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" +$langXlfFiles = @() +if ($allXlfFiles) { + $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' + $firstLangCode = $Matches.1 + $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" +} +$langXlfFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" + $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$locFiles = $jsonFiles + $jsonWinformsTemplateFiles + $xlfFiles + +$locJson = @{ + Projects = @( + @{ + LanguageSet = $LanguageSet + LocItems = @( + $locFiles | ForEach-Object { + $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) + { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { + Remove-Item -Path $sourceFile + } + if ($continue) + { + if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" + } + } else { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnName" + OutputPath = $outputPath + } + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "WiX_CloneLanguages" + LssFiles = @( "wxl_loc.lss" ) + LocItems = @( + $wxlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if ($continue) + { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + } + } + ) + }, + @{ + LanguageSet = $LanguageSet + CloneLanguageSet = "VS_macOS_CloneLanguages" + LssFiles = @( ".\eng\common\loc\P22DotNetHtmlLocalization.lss" ) + LocItems = @( + $macosHtmlFiles | ForEach-Object { + $outputPath = "$($_.Directory.FullName | Resolve-Path -Relative)\" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($_.FullName.Contains($exclusion)) { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + $lciFile = $sourceFile + ".lci" + if ($continue) { + $result = @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = $outputPath + } + if (Test-Path $lciFile -PathType Leaf) { + $result["LciFile"] = $lciFile + } + return $result + } + } + ) + } + ) +} + +$json = ConvertTo-Json $locJson -Depth 5 +Write-Host "LocProject.json generated:`n`n$json`n`n" +Pop-Location + +if (!$UseCheckedInLocProjectJson) { + New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json +} +else { + New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json + + if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { + Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." + + exit 1 + } + else { + Write-Host "Generated LocProject.json and current LocProject.json are identical." + } +} diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 new file mode 100644 index 000000000..a0c7d792a --- /dev/null +++ b/eng/common/generate-sbom-prep.ps1 @@ -0,0 +1,29 @@ +Param( + [Parameter(Mandatory=$true)][string] $ManifestDirPath # Manifest directory where sbom will be placed +) + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" +$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_' +$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName + +Write-Host "Artifact name before : $ArtifactName" +Write-Host "Artifact name after : $SafeArtifactName" + +Write-Host "Creating dir $ManifestDirPath" + +# create directory for sbom manifest to be placed +if (!(Test-Path -path $SbomGenerationDir)) +{ + New-Item -ItemType Directory -path $SbomGenerationDir + Write-Host "Successfully created directory $SbomGenerationDir" +} +else{ + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +} + +Write-Host "Updating artifact name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh new file mode 100644 index 000000000..bbb492215 --- /dev/null +++ b/eng/common/generate-sbom-prep.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" +. $scriptroot/pipeline-logging-functions.sh + +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" + +manifest_dir=$1 + +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +sbom_generation_dir="$manifest_dir/$safe_artifact_name" + +if [ ! -d "$sbom_generation_dir" ] ; then + mkdir -p "$sbom_generation_dir" + echo "Sbom directory created." $sbom_generation_dir +else + Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." +fi + +echo "Artifact name before : "$artifact_name +echo "Artifact name after : "$safe_artifact_name +export ARTIFACT_NAME=$safe_artifact_name +echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" + +exit 0 diff --git a/eng/common/helixpublish.proj b/eng/common/helixpublish.proj new file mode 100644 index 000000000..d7f185856 --- /dev/null +++ b/eng/common/helixpublish.proj @@ -0,0 +1,26 @@ + + + + msbuild + + + + + %(Identity) + + + + + + $(WorkItemDirectory) + $(WorkItemCommand) + $(WorkItemTimeout) + + + + + + + + + diff --git a/eng/common/init-tools-native.cmd b/eng/common/init-tools-native.cmd new file mode 100644 index 000000000..438cd548c --- /dev/null +++ b/eng/common/init-tools-native.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" +exit /b %ErrorLevel% \ No newline at end of file diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 new file mode 100644 index 000000000..27ccdb9ec --- /dev/null +++ b/eng/common/init-tools-native.ps1 @@ -0,0 +1,203 @@ +<# +.SYNOPSIS +Entry point script for installing native tools + +.DESCRIPTION +Reads $RepoRoot\global.json file to determine native assets to install +and executes installers for those tools + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER InstallDirectory +Directory to install native toolset. This is a command-line override for the default +Install directory precedence order: +- InstallDirectory command-line override +- NETCOREENG_INSTALL_DIRECTORY environment variable +- (default) %USERPROFILE%/.netcoreeng/native + +.PARAMETER Clean +Switch specifying to not install anything, but cleanup native asset folders + +.PARAMETER Force +Clean and then install tools + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.PARAMETER GlobalJsonFile +File path to global.json file + +.PARAMETER PathPromotion +Optional switch to enable either promote native tools specified in the global.json to the path (in Azure Pipelines) +or break the build if a native tool is not found on the path (on a local dev machine) + +.NOTES +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', + [string] $InstallDirectory, + [switch] $Clean = $False, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [string] $GlobalJsonFile, + [switch] $PathPromotion +) + +if (!$GlobalJsonFile) { + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' +} + +Set-StrictMode -version 2.0 +$ErrorActionPreference='Stop' + +. $PSScriptRoot\pipeline-logging-functions.ps1 +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq 'Continue' + + $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' + $NativeBaseDir = $InstallDirectory + if (!$NativeBaseDir) { + $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory + } + $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir + $InstallBin = Join-Path $NativeBaseDir 'bin' + $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' + + # Process tools list + Write-Host "Processing $GlobalJsonFile" + If (-Not (Test-Path $GlobalJsonFile)) { + Write-Host "Unable to find '$GlobalJsonFile'" + exit 0 + } + $NativeTools = Get-Content($GlobalJsonFile) -Raw | + ConvertFrom-Json | + Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue + if ($NativeTools) { + if ($PathPromotion -eq $True) { + $ArcadeToolsDirectory = "$env:SYSTEMDRIVE\arcade-tools" + if (Test-Path $ArcadeToolsDirectory) { # if this directory exists, we should use native tools on machine + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $InstalledTools = @{} + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + if ($ToolVersion -eq "latest") { + $ToolVersion = "" + } + $ToolDirectories = (Get-ChildItem -Path "$ArcadeToolsDirectory" -Filter "$ToolName-$ToolVersion*" | Sort-Object -Descending) + if ($ToolDirectories -eq $null) { + Write-Error "Unable to find directory for $ToolName $ToolVersion; please make sure the tool is installed on this image." + exit 1 + } + $ToolDirectory = $ToolDirectories[0] + $BinPathFile = "$($ToolDirectory.FullName)\binpath.txt" + if (-not (Test-Path -Path "$BinPathFile")) { + Write-Error "Unable to find binpath.txt in '$($ToolDirectory.FullName)' ($ToolName $ToolVersion); artifact is either installed incorrectly or is not a bootstrappable tool." + exit 1 + } + $BinPath = Get-Content "$BinPathFile" + $ToolPath = Convert-Path -Path $BinPath + Write-Host "Adding $ToolName to the path ($ToolPath)..." + Write-Host "##vso[task.prependpath]$ToolPath" + $env:PATH = "$ToolPath;$env:PATH" + $InstalledTools += @{ $ToolName = $ToolDirectory.FullName } + } + } + return $InstalledTools + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + + if ((Get-Command "$ToolName" -ErrorAction SilentlyContinue) -eq $null) { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "$ToolName not found on path. Please install $ToolName $ToolVersion before proceeding." + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message "If this is running on a build machine, the arcade-tools directory was not found, which means there's an error with the image." + } + } + exit 0 + } + } else { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $LocalInstallerArguments = @{ ToolName = "$ToolName" } + $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } + $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } + $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } + $LocalInstallerArguments += @{ Version = "$ToolVersion" } + + if ($Verbose) { + $LocalInstallerArguments += @{ Verbose = $True } + } + if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { + if($Force) { + $LocalInstallerArguments += @{ Force = $True } + } + } + if ($Clean) { + $LocalInstallerArguments += @{ Clean = $True } + } + + Write-Verbose "Installing $ToolName version $ToolVersion" + Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" + & $InstallerPath @LocalInstallerArguments + if ($LASTEXITCODE -Ne "0") { + $errMsg = "$ToolName installation failed" + if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { + $showNativeToolsWarning = $true + if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { + $showNativeToolsWarning = $false + } + if ($showNativeToolsWarning) { + Write-Warning $errMsg + } + $toolInstallationFailure = $true + } else { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg + exit 1 + } + } + } + + if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' + exit 1 + } + } + } + else { + Write-Host 'No native tools defined in global.json' + exit 0 + } + + if ($Clean) { + exit 0 + } + if (Test-Path $InstallBin) { + Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) + Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + return $InstallBin + } + elseif (-not ($PathPromotion)) { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' + exit 1 + } + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh new file mode 100644 index 000000000..3e6a8d6ac --- /dev/null +++ b/eng/common/init-tools-native.sh @@ -0,0 +1,238 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' +install_directory='' +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 +global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" +declare -a native_assets + +. $scriptroot/pipeline-logging-functions.sh +. $scriptroot/native/common-library.sh + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installdirectory) + install_directory=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --donotabortonfailure) + donotabortonfailure=true + shift 1 + ;; + --donotdisplaywarnings) + donotdisplaywarnings=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --installdirectory Directory to install native toolset." + echo " This is a command-line override for the default" + echo " Install directory precedence order:" + echo " - InstallDirectory command-line override" + echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" + echo " - (default) %USERPROFILE%/.netcoreeng/native" + echo "" + echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" + echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" + echo " --force Clean and then install tools" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --baseuri Base URI for where to download native tools from" + echo " --downloadretries Number of times a download should be attempted" + echo " --retrywaittimeseconds Wait time between download attempts" + echo "" + exit 0 + ;; + esac +done + +function ReadGlobalJsonNativeTools { + # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! + if command -v jq &> /dev/null; then + + # jq: read each key/value pair under "native-tools" entry and emit: + # KEY="" VALUE="" + # followed by a null byte. + # + # bash: read line with null byte delimeter and push to array (for later `eval`uation). + + while IFS= read -rd '' line; do + native_assets+=("$line") + done < <(jq -r '. | + select(has("native-tools")) | + ."native-tools" | + keys[] as $k | + @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") + + return + fi + + # Warning: falling back to manually parsing JSON, which is not recommended. + + # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. + # It has been tested with several weird strings with escaped characters in entries (key and value) + # and results were compared with the output of jq(1) in binary representation using xxd(1); + # just before the assignment to 'native_assets' array (above and below). + + # try to capture the section under "native-tools". + if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then + return + fi + + section="${BASH_REMATCH[1]}" + + parseStarted=0 + possibleEnd=0 + escaping=0 + escaped=0 + isKey=1 + + for (( i=0; i<${#section}; i++ )); do + char="${section:$i:1}" + if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi + + if ! ((escaping)) && [[ "$char" == "\\" ]]; then + escaping=1 + elif ((escaping)) && ! ((escaped)); then + escaped=1 + fi + + if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then + parseStarted=1 + possibleEnd=0 + elif [[ "$char" == "'" ]]; then + token="$token'\\\''" + possibleEnd=0 + elif ((escaping)) || [[ "$char" != "\"" ]]; then + token="$token$char" + possibleEnd=1 + fi + + if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then + # Use printf to unescape token to match jq(1)'s @sh formatting rules. + # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. + printf -v token "'$token'" + + if ((isKey)); then + KEY="$token" + isKey=0 + else + line="KEY=$KEY VALUE=$token" + native_assets+=("$line") + isKey=1 + fi + + # reset for next token + parseStarted=0 + token= + elif ((escaping)) && ((escaped)); then + escaping=0 + escaped=0 + fi + done +} + +native_base_dir=$install_directory +if [[ -z $install_directory ]]; then + native_base_dir=$(GetNativeInstallDirectory) +fi + +install_bin="${native_base_dir}/bin" +installed_any=false + +ReadGlobalJsonNativeTools + +if [[ ${#native_assets[@]} -eq 0 ]]; then + echo "No native tools defined in global.json" + exit 0; +else + native_installer_dir="$scriptroot/native" + for index in "${!native_assets[@]}"; do + eval "${native_assets["$index"]}" + + installer_path="$native_installer_dir/install-$KEY.sh" + installer_command="$installer_path" + installer_command+=" --baseuri $base_uri" + installer_command+=" --installpath $install_bin" + installer_command+=" --version $VALUE" + echo $installer_command + + if [[ $force = true ]]; then + installer_command+=" --force" + fi + + if [[ $clean = true ]]; then + installer_command+=" --clean" + fi + + if [[ -a $installer_path ]]; then + $installer_command + if [[ $? != 0 ]]; then + if [[ $donotabortonfailure = true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + exit 1 + fi + else + $installed_any = true + fi + else + if [[ $donotabortonfailure == true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + exit 1 + fi + fi + done +fi + +if [[ $clean = true ]]; then + exit 0 +fi + +if [[ -d $install_bin ]]; then + echo "Native tools are available from $install_bin" + echo "##vso[task.prependpath]$install_bin" +else + if [[ $installed_any = true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" + exit 1 + fi +fi + +exit 0 diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 new file mode 100644 index 000000000..92b77347d --- /dev/null +++ b/eng/common/internal-feed-operations.ps1 @@ -0,0 +1,132 @@ +param( + [Parameter(Mandatory=$true)][string] $Operation, + [string] $AuthToken, + [string] $CommitSha, + [string] $RepoName, + [switch] $IsFeedPrivate +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +. $PSScriptRoot\tools.ps1 + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified +# internal builds +function SetupCredProvider { + param( + [string] $AuthToken + ) + + # Install the Cred Provider NuGet plugin + Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' + Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' + + Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." + Invoke-WebRequest $url -OutFile installcredprovider.ps1 + + Write-Host 'Installing plugin...' + .\installcredprovider.ps1 -Force + + Write-Host "Deleting local copy of 'installcredprovider.ps1'..." + Remove-Item .\installcredprovider.ps1 + + if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { + Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + } + else { + Write-Host 'CredProvider plugin was installed correctly!' + } + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + $nugetConfigPath = Join-Path $RepoRoot "NuGet.config" + + if (-Not (Test-Path -Path $nugetConfigPath)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' + ExitWithExitCode 1 + } + + $endpoints = New-Object System.Collections.ArrayList + $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} + + if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { + foreach ($stableRestoreResource in $nugetConfigPackageSources) { + $trimmedResource = ([string]$stableRestoreResource).Trim() + [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) + } + } + + if (($endpoints | Measure-Object).Count -gt 0) { + $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress + + # Create the environment variables the AzDo way + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ + 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' + 'issecret' = 'false' + } + + # We don't want sessions cached since we will be updating the endpoints quite frequently + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ + 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' + 'issecret' = 'false' + } + } + else + { + Write-Host 'No internal endpoints found in NuGet.config' + } +} + +#Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + $dotnetTempDir = Join-Path $RepoRoot "dotnet" + $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + $dotnet = "$dotnetTempDir\dotnet.exe" + $restoreProjPath = "$PSScriptRoot\restore.proj" + + Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + '' | Out-File "$restoreProjPath" + + & $dotnet restore $restoreProjPath + + Write-Host 'Arcade SDK restored!' + + if (Test-Path -Path $restoreProjPath) { + Remove-Item $restoreProjPath + } + + if (Test-Path -Path $dotnetTempDir) { + Remove-Item $dotnetTempDir -Recurse + } +} + +try { + Push-Location $PSScriptRoot + + if ($Operation -like 'setup') { + SetupCredProvider $AuthToken + } + elseif ($Operation -like 'install-restore') { + InstallDotNetSdkAndRestoreArcade + } + else { + Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" + ExitWithExitCode 1 + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ + ExitWithExitCode 1 +} +finally { + Pop-Location +} diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh new file mode 100644 index 000000000..9378223ba --- /dev/null +++ b/eng/common/internal-feed-operations.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -e + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. +# This should ONLY be called from identified internal builds +function SetupCredProvider { + local authToken=$1 + + # Install the Cred Provider NuGet plugin + echo "Setting up Cred Provider NuGet plugin in the agent..."... + echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" + + echo "Writing the contents of 'installcredprovider.ps1' locally..." + local installcredproviderPath="installcredprovider.sh" + if command -v curl > /dev/null; then + curl $url > "$installcredproviderPath" + else + wget -q -O "$installcredproviderPath" "$url" + fi + + echo "Installing plugin..." + . "$installcredproviderPath" + + echo "Deleting local copy of 'installcredprovider.sh'..." + rm installcredprovider.sh + + if [ ! -d "$HOME/.nuget/plugins" ]; then + Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + else + echo "CredProvider plugin was installed correctly!" + fi + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + local nugetConfigPath="{$repo_root}NuGet.config" + + if [ ! "$nugetConfigPath" ]; then + Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" + ExitWithExitCode 1 + fi + + local endpoints='[' + local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` + local pattern="value=\"(.*)\"" + + for value in $nugetConfigPackageValues + do + if [[ $value =~ $pattern ]]; then + local endpoint="${BASH_REMATCH[1]}" + endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," + fi + done + + endpoints=${endpoints%?} + endpoints+=']' + + if [ ${#endpoints} -gt 2 ]; then + local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" + + echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" + echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" + else + echo "No internal endpoints found in NuGet.config" + fi +} + +# Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + local dotnetTempDir="$repo_root/dotnet" + local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + local restoreProjPath="$repo_root/eng/common/restore.proj" + + echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + echo "" > "$restoreProjPath" + + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + local res=`$dotnetTempDir/dotnet restore $restoreProjPath` + echo "Arcade SDK restored!" + + # Cleanup + if [ "$restoreProjPath" ]; then + rm "$restoreProjPath" + fi + + if [ "$dotnetTempDir" ]; then + rm -r $dotnetTempDir + fi +} + +source="${BASH_SOURCE[0]}" +operation='' +authToken='' +repoName='' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --operation) + operation=$2 + shift + ;; + --authtoken) + authToken=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ "$operation" = "setup" ]; then + SetupCredProvider $authToken +elif [ "$operation" = "install-restore" ]; then + InstallDotNetSdkAndRestoreArcade +else + echo "Unknown operation '$operation'!" +fi diff --git a/eng/common/internal/Directory.Build.props b/eng/common/internal/Directory.Build.props new file mode 100644 index 000000000..dbf99d82a --- /dev/null +++ b/eng/common/internal/Directory.Build.props @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/internal/NuGet.config b/eng/common/internal/NuGet.config new file mode 100644 index 000000000..19d3d311b --- /dev/null +++ b/eng/common/internal/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj new file mode 100644 index 000000000..7f5ce6d60 --- /dev/null +++ b/eng/common/internal/Tools.csproj @@ -0,0 +1,30 @@ + + + + net472 + false + false + + + + + + + + + + + + + + https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + + $(RestoreSources); + https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; + + + + + + diff --git a/eng/common/loc/P22DotNetHtmlLocalization.lss b/eng/common/loc/P22DotNetHtmlLocalization.lss new file mode 100644 index 000000000..5d892d619 --- /dev/null +++ b/eng/common/loc/P22DotNetHtmlLocalization.lss @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 new file mode 100644 index 000000000..f041e5ddd --- /dev/null +++ b/eng/common/msbuild.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch] $ci, + [switch] $prepareMachine, + [switch] $excludePrereleaseVS, + [string] $msbuildEngine = $null, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs +) + +. $PSScriptRoot\tools.ps1 + +try { + if ($ci) { + $nodeReuse = $false + } + + MSBuild @extraArgs +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh new file mode 100644 index 000000000..20d3dad54 --- /dev/null +++ b/eng/common/msbuild.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +verbosity='minimal' +warn_as_error=true +node_reuse=true +prepare_machine=false +extra_args='' + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --verbosity) + verbosity=$2 + shift 2 + ;; + --warnaserror) + warn_as_error=$2 + shift 2 + ;; + --nodereuse) + node_reuse=$2 + shift 2 + ;; + --ci) + ci=true + shift 1 + ;; + --preparemachine) + prepare_machine=true + shift 1 + ;; + *) + extra_args="$extra_args $1" + shift 1 + ;; + esac +done + +. "$scriptroot/tools.sh" + +if [[ "$ci" == true ]]; then + node_reuse=false +fi + +MSBuild $extra_args +ExitWithExitCode 0 diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 new file mode 100644 index 000000000..ca38268c4 --- /dev/null +++ b/eng/common/native/CommonLibrary.psm1 @@ -0,0 +1,400 @@ +<# +.SYNOPSIS +Helper module to install an archive to a directory + +.DESCRIPTION +Helper module to download and extract an archive to a specified directory + +.PARAMETER Uri +Uri of artifact to download + +.PARAMETER InstallDirectory +Directory to extract artifact contents to + +.PARAMETER Force +Force download / extraction if file or contents already exist. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds. Default = 30 + +.NOTES +Returns False if download or extraction fail, True otherwise +#> +function DownloadAndExtract { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $InstallDirectory, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 + ) + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri + + # Download native tool + $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$Force ` + -Verbose:$Verbose + + if ($DownloadStatus -Eq $False) { + Write-Error "Download failed from $Uri" + return $False + } + + # Extract native tool + $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$Force ` + -Verbose:$Verbose + + if ($UnzipStatus -Eq $False) { + # Retry Download one more time with Force=true + $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries 1 ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$True ` + -Verbose:$Verbose + + if ($DownloadRetryStatus -Eq $False) { + Write-Error "Last attempt of download failed as well" + return $False + } + + # Retry unzip again one more time with Force=true + $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$True ` + -Verbose:$Verbose + if ($UnzipRetryStatus -Eq $False) + { + Write-Error "Last attempt of unzip failed as well" + # Clean up partial zips and extracts + if (Test-Path $TempToolPath) { + Remove-Item $TempToolPath -Force + } + if (Test-Path $InstallDirectory) { + Remove-Item $InstallDirectory -Force -Recurse + } + return $False + } + } + + return $True +} + +<# +.SYNOPSIS +Download a file, retry on failure + +.DESCRIPTION +Download specified file and retry if attempt fails + +.PARAMETER Uri +Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded + +.PARAMETER Path +Path to download or copy uri file to + +.PARAMETER Force +Overwrite existing file if present. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds Default = 30 + +#> +function Get-File { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $Path, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [switch] $Force = $False + ) + $Attempt = 0 + + if ($Force) { + if (Test-Path $Path) { + Remove-Item $Path -Force + } + } + if (Test-Path $Path) { + Write-Host "File '$Path' already exists, skipping download" + return $True + } + + $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent + if (-Not (Test-Path $DownloadDirectory)) { + New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null + } + + $TempPath = "$Path.tmp" + if (Test-Path -IsValid -Path $Uri) { + Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" + Copy-Item -Path $Uri -Destination $TempPath + Write-Verbose "Moving temporary file to '$Path'" + Move-Item -Path $TempPath -Destination $Path + return $? + } + else { + Write-Verbose "Downloading $Uri" + # Don't display the console progress UI - it's a huge perf hit + $ProgressPreference = 'SilentlyContinue' + while($Attempt -Lt $DownloadRetries) + { + try { + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath + Write-Verbose "Downloaded to temporary location '$TempPath'" + Move-Item -Path $TempPath -Destination $Path + Write-Verbose "Moved temporary file to '$Path'" + return $True + } + catch { + $Attempt++ + if ($Attempt -Lt $DownloadRetries) { + $AttemptsLeft = $DownloadRetries - $Attempt + Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $RetryWaitTimeInSeconds + } + else { + Write-Error $_ + Write-Error $_.Exception + } + } + } + } + + return $False +} + +<# +.SYNOPSIS +Generate a shim for a native tool + +.DESCRIPTION +Creates a wrapper script (shim) that passes arguments forward to native tool assembly + +.PARAMETER ShimName +The name of the shim + +.PARAMETER ShimDirectory +The directory where shims are stored + +.PARAMETER ToolFilePath +Path to file that shim forwards to + +.PARAMETER Force +Replace shim if already present. Default = False + +.NOTES +Returns $True if generating shim succeeds, $False otherwise +#> +function New-ScriptShim { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ShimName, + [Parameter(Mandatory=$True)] + [string] $ShimDirectory, + [Parameter(Mandatory=$True)] + [string] $ToolFilePath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [switch] $Force + ) + try { + Write-Verbose "Generating '$ShimName' shim" + + if (-Not (Test-Path $ToolFilePath)){ + Write-Error "Specified tool file path '$ToolFilePath' does not exist" + return $False + } + + # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs + # Many of the checks for installed programs expect a .exe extension for Windows tools, rather + # than a .bat or .cmd file. + # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer + if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { + $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` + -InstallDirectory $ShimDirectory\WinShimmer ` + -Force:$Force ` + -DownloadRetries 2 ` + -RetryWaitTimeInSeconds 5 ` + -Verbose:$Verbose + } + + if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { + Write-Host "$ShimName.exe already exists; replacing..." + Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") + } + + & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory + return $True + } + catch { + Write-Host $_ + Write-Host $_.Exception + return $False + } +} + +<# +.SYNOPSIS +Returns the machine architecture of the host machine + +.NOTES +Returns 'x64' on 64 bit machines + Returns 'x86' on 32 bit machines +#> +function Get-MachineArchitecture { + $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE + $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 + if($ProcessorArchitecture -Eq "X86") + { + if(($ProcessorArchitectureW6432 -Eq "") -Or + ($ProcessorArchitectureW6432 -Eq "X86")) { + return "x86" + } + $ProcessorArchitecture = $ProcessorArchitectureW6432 + } + if (($ProcessorArchitecture -Eq "AMD64") -Or + ($ProcessorArchitecture -Eq "IA64") -Or + ($ProcessorArchitecture -Eq "ARM64") -Or + ($ProcessorArchitecture -Eq "LOONGARCH64")) { + return "x64" + } + return "x86" +} + +<# +.SYNOPSIS +Get the name of a temporary folder under the native install directory +#> +function Get-TempDirectory { + return Join-Path (Get-NativeInstallDirectory) "temp/" +} + +function Get-TempPathFilename { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Path + ) + $TempDir = CommonLibrary\Get-TempDirectory + $TempFilename = Split-Path $Path -leaf + $TempPath = Join-Path $TempDir $TempFilename + return $TempPath +} + +<# +.SYNOPSIS +Returns the base directory to use for native tool installation + +.NOTES +Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable +is set, or otherwise returns an install directory under the %USERPROFILE% +#> +function Get-NativeInstallDirectory { + $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY + if (!$InstallDir) { + $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" + } + return $InstallDir +} + +<# +.SYNOPSIS +Unzip an archive + +.DESCRIPTION +Powershell module to unzip an archive to a specified directory + +.PARAMETER ZipPath (Required) +Path to archive to unzip + +.PARAMETER OutputDirectory (Required) +Output directory for archive contents + +.PARAMETER Force +Overwrite output directory contents if they already exist + +.NOTES +- Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. +- Returns True if unzip operation is successful +- Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory +- Returns False if unable to extract zip archive +#> +function Expand-Zip { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ZipPath, + [Parameter(Mandatory=$True)] + [string] $OutputDirectory, + [switch] $Force + ) + + Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" + try { + if ((Test-Path $OutputDirectory) -And (-Not $Force)) { + Write-Host "Directory '$OutputDirectory' already exists, skipping extract" + return $True + } + if (Test-Path $OutputDirectory) { + Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" + Remove-Item $OutputDirectory -Force -Recurse + if ($? -Eq $False) { + Write-Error "Unable to remove '$OutputDirectory'" + return $False + } + } + + $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" + if (Test-Path $TempOutputDirectory) { + Remove-Item $TempOutputDirectory -Force -Recurse + } + New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null + + Add-Type -assembly "system.io.compression.filesystem" + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") + if ($? -Eq $False) { + Write-Error "Unable to extract '$ZipPath'" + return $False + } + + Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory + } + catch { + Write-Host $_ + Write-Host $_.Exception + + return $False + } + return $True +} + +export-modulemember -function DownloadAndExtract +export-modulemember -function Expand-Zip +export-modulemember -function Get-File +export-modulemember -function Get-MachineArchitecture +export-modulemember -function Get-NativeInstallDirectory +export-modulemember -function Get-TempDirectory +export-modulemember -function Get-TempPathFilename +export-modulemember -function New-ScriptShim diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh new file mode 100644 index 000000000..080c2c283 --- /dev/null +++ b/eng/common/native/common-library.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +function GetNativeInstallDirectory { + local install_dir + + if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then + install_dir=$HOME/.netcoreeng/native/ + else + install_dir=$NETCOREENG_INSTALL_DIRECTORY + fi + + echo $install_dir + return 0 +} + +function GetTempDirectory { + + echo $(GetNativeInstallDirectory)temp/ + return 0 +} + +function ExpandZip { + local zip_path=$1 + local output_directory=$2 + local force=${3:-false} + + echo "Extracting $zip_path to $output_directory" + if [[ -d $output_directory ]] && [[ $force = false ]]; then + echo "Directory '$output_directory' already exists, skipping extract" + return 0 + fi + + if [[ -d $output_directory ]]; then + echo "'Force flag enabled, but '$output_directory' exists. Removing directory" + rm -rf $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" + return 1 + fi + fi + + echo "Creating directory: '$output_directory'" + mkdir -p $output_directory + + echo "Extracting archive" + tar -xf $zip_path -C $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" + return 1 + fi + + return 0 +} + +function GetCurrentOS { + local unameOut="$(uname -s)" + case $unameOut in + Linux*) echo "Linux";; + Darwin*) echo "MacOS";; + esac + return 0 +} + +function GetFile { + local uri=$1 + local path=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + if [[ -f $path ]]; then + if [[ $force = false ]]; then + echo "File '$path' already exists. Skipping download" + return 0 + else + rm -rf $path + fi + fi + + if [[ -f $uri ]]; then + echo "'$uri' is a file path, copying file to '$path'" + cp $uri $path + return $? + fi + + echo "Downloading $uri" + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail + else + wget -q -O "$path" "$uri" --tries="$download_retries" + fi + + return $? +} + +function GetTempPathFileName { + local path=$1 + + local temp_dir=$(GetTempDirectory) + local temp_file_name=$(basename $path) + echo $temp_dir$temp_file_name + return 0 +} + +function DownloadAndExtract { + local uri=$1 + local installDir=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + local temp_tool_path=$(GetTempPathFileName $uri) + + echo "downloading to: $temp_tool_path" + + # Download file + GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." + return 1 + fi + + # Extract File + echo "extracting from $temp_tool_path to $installDir" + ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." + return 1 + fi + + return 0 +} + +function NewScriptShim { + local shimpath=$1 + local tool_file_path=$2 + local force=${3:-false} + + echo "Generating '$shimpath' shim" + if [[ -f $shimpath ]]; then + if [[ $force = false ]]; then + echo "File '$shimpath' already exists." >&2 + return 1 + else + rm -rf $shimpath + fi + fi + + if [[ ! -f $tool_file_path ]]; then + # try to see if the path is lower cased + tool_file_path="$(echo $tool_file_path | tr "[:upper:]" "[:lower:]")" + if [[ ! -f $tool_file_path ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" + return 1 + fi + fi + + local shim_contents=$'#!/usr/bin/env bash\n' + shim_contents+="SHIMARGS="$'$1\n' + shim_contents+="$tool_file_path"$' $SHIMARGS\n' + + # Write shim file + echo "$shim_contents" > $shimpath + + chmod +x $shimpath + + echo "Finished generating shim '$shimpath'" + + return $? +} + diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh new file mode 100644 index 000000000..2d5660642 --- /dev/null +++ b/eng/common/native/init-compiler.sh @@ -0,0 +1,137 @@ +#!/bin/sh +# +# This file detects the C/C++ compiler and exports it to the CC/CXX environment variables +# +# NOTE: some scripts source this file and rely on stdout being empty, make sure to not output anything here! + +if [ -z "$build_arch" ] || [ -z "$compiler" ]; then + echo "Usage..." + echo "build_arch= compiler= init-compiler.sh" + echo "Specify the target architecture." + echo "Specify the name of compiler (clang or gcc)." + exit 1 +fi + +case "$compiler" in + clang*|-clang*|--clang*) + # clangx.y or clang-x.y + version="$(echo "$compiler" | tr -d '[:alpha:]-=')" + majorVersion="${version%%.*}" + [ -z "${version##*.*}" ] && minorVersion="${version#*.}" + + if [ -z "$minorVersion" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -le 6 ]; then + minorVersion=0; + fi + compiler=clang + ;; + + gcc*|-gcc*|--gcc*) + # gccx.y or gcc-x.y + version="$(echo "$compiler" | tr -d '[:alpha:]-=')" + majorVersion="${version%%.*}" + [ -z "${version##*.*}" ] && minorVersion="${version#*.}" + compiler=gcc + ;; +esac + +cxxCompiler="$compiler++" + +# clear the existing CC and CXX from environment +CC= +CXX= +LDFLAGS= + +if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi + +check_version_exists() { + desired_version=-1 + + # Set up the environment to be used for building with the desired compiler. + if command -v "$compiler-$1.$2" > /dev/null; then + desired_version="-$1.$2" + elif command -v "$compiler$1$2" > /dev/null; then + desired_version="$1$2" + elif command -v "$compiler-$1$2" > /dev/null; then + desired_version="-$1$2" + fi + + echo "$desired_version" +} + +if [ -z "$CLR_CC" ]; then + + # Set default versions + if [ -z "$majorVersion" ]; then + # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. + if [ "$compiler" = "clang" ]; then versions="18 17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi + + for version in $versions; do + _major="${version%%.*}" + [ -z "${version##*.*}" ] && _minor="${version#*.}" + desired_version="$(check_version_exists "$_major" "$_minor")" + if [ "$desired_version" != "-1" ]; then majorVersion="$_major"; break; fi + done + + if [ -z "$majorVersion" ]; then + if command -v "$compiler" > /dev/null; then + if [ "$(uname)" != "Darwin" ]; then + echo "Warning: Specific version of $compiler not found, falling back to use the one in PATH." + fi + CC="$(command -v "$compiler")" + CXX="$(command -v "$cxxCompiler")" + else + echo "No usable version of $compiler found." + exit 1 + fi + else + if [ "$compiler" = "clang" ] && [ "$majorVersion" -lt 5 ]; then + if [ "$build_arch" = "arm" ] || [ "$build_arch" = "armel" ]; then + if command -v "$compiler" > /dev/null; then + echo "Warning: Found clang version $majorVersion which is not supported on arm/armel architectures, falling back to use clang from PATH." + CC="$(command -v "$compiler")" + CXX="$(command -v "$cxxCompiler")" + else + echo "Found clang version $majorVersion which is not supported on arm/armel architectures, and there is no clang in PATH." + exit 1 + fi + fi + fi + fi + else + desired_version="$(check_version_exists "$majorVersion" "$minorVersion")" + if [ "$desired_version" = "-1" ]; then + echo "Could not find specific version of $compiler: $majorVersion $minorVersion." + exit 1 + fi + fi + + if [ -z "$CC" ]; then + CC="$(command -v "$compiler$desired_version")" + CXX="$(command -v "$cxxCompiler$desired_version")" + if [ -z "$CXX" ]; then CXX="$(command -v "$cxxCompiler")"; fi + fi +else + if [ ! -f "$CLR_CC" ]; then + echo "CLR_CC is set but path '$CLR_CC' does not exist" + exit 1 + fi + CC="$CLR_CC" + CXX="$CLR_CXX" +fi + +if [ -z "$CC" ]; then + echo "Unable to find $compiler." + exit 1 +fi + +# Only lld version >= 9 can be considered stable. lld doesn't support s390x. +if [ "$compiler" = "clang" ] && [ -n "$majorVersion" ] && [ "$majorVersion" -ge 9 ] && [ "$build_arch" != "s390x" ]; then + if "$CC" -fuse-ld=lld -Wl,--version >/dev/null 2>&1; then + LDFLAGS="-fuse-ld=lld" + fi +fi + +SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version")" + +export CC CXX LDFLAGS SCAN_BUILD_COMMAND diff --git a/eng/common/native/init-distro-rid.sh b/eng/common/native/init-distro-rid.sh new file mode 100644 index 000000000..de1687b2c --- /dev/null +++ b/eng/common/native/init-distro-rid.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +# getNonPortableDistroRid +# +# Input: +# targetOs: (str) +# targetArch: (str) +# rootfsDir: (str) +# +# Return: +# non-portable rid +getNonPortableDistroRid() +{ + local targetOs="$1" + local targetArch="$2" + local rootfsDir="$3" + local nonPortableRid="" + + if [ "$targetOs" = "linux" ]; then + if [ -e "${rootfsDir}/etc/os-release" ]; then + source "${rootfsDir}/etc/os-release" + + if [[ "${ID}" == "rhel" || "${ID}" == "rocky" || "${ID}" == "alpine" ]]; then + # remove the last version digit + VERSION_ID="${VERSION_ID%.*}" + fi + + if [[ "${VERSION_ID:-}" =~ ^([[:digit:]]|\.)+$ ]]; then + nonPortableRid="${ID}.${VERSION_ID}-${targetArch}" + else + # Rolling release distros either do not set VERSION_ID, set it as blank or + # set it to non-version looking string (such as TEMPLATE_VERSION_ID on ArchLinux); + # so omit it here to be consistent with everything else. + nonPortableRid="${ID}-${targetArch}" + fi + + elif [ -e "${rootfsDir}/android_platform" ]; then + source "$rootfsDir"/android_platform + nonPortableRid="$RID" + fi + fi + + if [ "$targetOs" = "freebsd" ]; then + # $rootfsDir can be empty. freebsd-version is shell script and it should always work. + __freebsd_major_version=$($rootfsDir/bin/freebsd-version | { read v; echo "${v%%.*}"; }) + nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" + elif command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then + __android_sdk_version=$(getprop ro.build.version.sdk) + nonPortableRid="android.$__android_sdk_version-${targetArch}" + elif [ "$targetOs" = "illumos" ]; then + __uname_version=$(uname -v) + case "$__uname_version" in + omnios-*) + __omnios_major_version=$(echo "${__uname_version:8:2}") + nonPortableRid=omnios."$__omnios_major_version"-"$targetArch" + ;; + joyent_*) + __smartos_major_version=$(echo "${__uname_version:7:4}") + nonPortableRid=smartos."$__smartos_major_version"-"$targetArch" + ;; + illumos_*) + nonPortableRid=openindiana-"$targetArch" + ;; + esac + elif [ "$targetOs" = "solaris" ]; then + __uname_version=$(uname -v) + __solaris_major_version=$(echo "${__uname_version%.*}") + nonPortableRid=solaris."$__solaris_major_version"-"$targetArch" + elif [ "$targetOs" = "haiku" ]; then + __uname_release=$(uname -r) + nonPortableRid=haiku.r"$__uname_release"-"$targetArch" + fi + + echo "$(echo $nonPortableRid | tr '[:upper:]' '[:lower:]')" +} + +# initDistroRidGlobal +# +# Input: +# os: (str) +# arch: (str) +# rootfsDir?: (nullable:string) +# +# Return: +# None +# +# Notes: +# +# It is important to note that the function does not return anything, but it +# exports the following variables on success: +# +# __DistroRid : Non-portable rid of the target platform. +# __PortableTargetOS : OS-part of the portable rid that corresponds to the target platform. +# +initDistroRidGlobal() +{ + local targetOs="$1" + local targetArch="$2" + local rootfsDir="" + if [ "$#" -ge 3 ]; then + rootfsDir="$3" + fi + + if [ -n "${rootfsDir}" ]; then + # We may have a cross build. Check for the existence of the rootfsDir + if [ ! -e "${rootfsDir}" ]; then + echo "Error rootfsDir has been passed, but the location is not valid." + exit 1 + fi + fi + + __DistroRid=$(getNonPortableDistroRid "${targetOs}" "${targetArch}" "${rootfsDir}") + + if [ -z "${__PortableTargetOS:-}" ]; then + __PortableTargetOS="$targetOs" + + STRINGS="$(command -v strings || true)" + if [ -z "$STRINGS" ]; then + STRINGS="$(command -v llvm-strings || true)" + fi + + # Check for musl-based distros (e.g Alpine Linux, Void Linux). + if "${rootfsDir}/usr/bin/ldd" --version 2>&1 | grep -q musl || + ( [ -n "$STRINGS" ] && "$STRINGS" "${rootfsDir}/usr/bin/ldd" 2>&1 | grep -q musl ); then + __PortableTargetOS="linux-musl" + fi + fi + + export __DistroRid __PortableTargetOS +} diff --git a/eng/common/native/init-os-and-arch.sh b/eng/common/native/init-os-and-arch.sh new file mode 100644 index 000000000..e693617a6 --- /dev/null +++ b/eng/common/native/init-os-and-arch.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Use uname to determine what the OS is. +OSName=$(uname -s | tr '[:upper:]' '[:lower:]') + +if command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then + OSName="android" +fi + +case "$OSName" in +freebsd|linux|netbsd|openbsd|sunos|android|haiku) + os="$OSName" ;; +darwin) + os=osx ;; +*) + echo "Unsupported OS $OSName detected!" + exit 1 ;; +esac + +# On Solaris, `uname -m` is discouraged, see https://docs.oracle.com/cd/E36784_01/html/E36870/uname-1.html +# and `uname -p` returns processor type (e.g. i386 on amd64). +# The appropriate tool to determine CPU is isainfo(1) https://docs.oracle.com/cd/E36784_01/html/E36870/isainfo-1.html. +if [ "$os" = "sunos" ]; then + if uname -o 2>&1 | grep -q illumos; then + os="illumos" + else + os="solaris" + fi + CPUName=$(isainfo -n) +else + # For the rest of the operating systems, use uname(1) to determine what the CPU is. + CPUName=$(uname -m) +fi + +case "$CPUName" in + arm64|aarch64) + arch=arm64 + ;; + + loongarch64) + arch=loongarch64 + ;; + + riscv64) + arch=riscv64 + ;; + + amd64|x86_64) + arch=x64 + ;; + + armv7l|armv8l) + if (NAME=""; . /etc/os-release; test "$NAME" = "Tizen"); then + arch=armel + else + arch=arm + fi + ;; + + armv6l) + arch=armv6 + ;; + + i[3-6]86) + echo "Unsupported CPU $CPUName detected, build might not succeed!" + arch=x86 + ;; + + s390x) + arch=s390x + ;; + + ppc64le) + arch=ppc64le + ;; + *) + echo "Unknown CPU $CPUName detected!" + exit 1 + ;; +esac diff --git a/eng/common/native/install-cmake-test.sh b/eng/common/native/install-cmake-test.sh new file mode 100644 index 000000000..8a5e7cf0d --- /dev/null +++ b/eng/common/native/install-cmake-test.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake-test" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh new file mode 100644 index 000000000..de496beeb --- /dev/null +++ b/eng/common/native/install-cmake.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 new file mode 100644 index 000000000..78f2d84a4 --- /dev/null +++ b/eng/common/native/install-tool.ps1 @@ -0,0 +1,132 @@ +<# +.SYNOPSIS +Install native tool + +.DESCRIPTION +Install cmake native tool from Azure blob storage + +.PARAMETER InstallPath +Base directory to install native tool to + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER CommonLibraryDirectory +Path to folder containing common library modules + +.PARAMETER Force +Force install of tools even if they previously exist + +.PARAMETER Clean +Don't install the tool, just clean up the current install of the tool + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.NOTES +Returns 0 if install succeeds, 1 otherwise +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [Parameter(Mandatory=$True)] + [string] $ToolName, + [Parameter(Mandatory=$True)] + [string] $InstallPath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [Parameter(Mandatory=$True)] + [string] $Version, + [string] $CommonLibraryDirectory = $PSScriptRoot, + [switch] $Force = $False, + [switch] $Clean = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 +) + +. $PSScriptRoot\..\pipeline-logging-functions.ps1 + +# Import common library modules +Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $Arch = CommonLibrary\Get-MachineArchitecture + $ToolOs = "win64" + if($Arch -Eq "x32") { + $ToolOs = "win32" + } + $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" + $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" + $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" + $ShimPath = Join-Path $InstallPath "$ToolName.exe" + + if ($Clean) { + Write-Host "Cleaning $ToolInstallDirectory" + if (Test-Path $ToolInstallDirectory) { + Remove-Item $ToolInstallDirectory -Force -Recurse + } + Write-Host "Cleaning $ShimPath" + if (Test-Path $ShimPath) { + Remove-Item $ShimPath -Force + } + $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri + Write-Host "Cleaning $ToolTempPath" + if (Test-Path $ToolTempPath) { + Remove-Item $ToolTempPath -Force + } + exit 0 + } + + # Install tool + if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { + Write-Verbose "$ToolName ($Version) already exists, skipping install" + } + else { + $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` + -InstallDirectory $ToolInstallDirectory ` + -Force:$Force ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Verbose:$Verbose + + if ($InstallStatus -Eq $False) { + Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" + exit 1 + } + } + + $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } + if (@($ToolFilePath).Length -Gt 1) { + Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" + exit 1 + } elseif (@($ToolFilePath).Length -Lt 1) { + Write-Host "$ToolName was not found in $ToolInstallDirectory." + exit 1 + } + + # Generate shim + # Always rewrite shims so that we are referencing the expected version + $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` + -ShimDirectory $InstallPath ` + -ToolFilePath "$ToolFilePath" ` + -BaseUri $BaseUri ` + -Force:$Force ` + -Verbose:$Verbose + + if ($GenerateShimStatus -Eq $False) { + Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" + return 1 + } + + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ + exit 1 +} diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 new file mode 100644 index 000000000..8e422c561 --- /dev/null +++ b/eng/common/pipeline-logging-functions.ps1 @@ -0,0 +1,260 @@ +# Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. + +# NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 + +$script:loggingCommandPrefix = '##vso[' +$script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? + New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } + New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } + New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } + New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } +) +# TODO: BUG: Escape % ??? +# TODO: Add test to verify don't need to escape "=". + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTelemetryError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Category, + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force) + + $PSBoundParameters.Remove('Category') | Out-Null + + if ($Force -Or ((Test-Path variable:ci) -And $ci)) { + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + } + $PSBoundParameters.Remove('Message') | Out-Null + $PSBoundParameters.Add('Message', $Message) + Write-PipelineTaskError @PSBoundParameters +} + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTaskError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force + ) + + if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { + if ($Type -eq 'error') { + Write-Host $Message -ForegroundColor Red + return + } + elseif ($Type -eq 'warning') { + Write-Host $Message -ForegroundColor Yellow + return + } + } + + if (($Type -ne 'error') -and ($Type -ne 'warning')) { + Write-Host $Message + return + } + $PSBoundParameters.Remove('Force') | Out-Null + if (-not $PSBoundParameters.ContainsKey('Type')) { + $PSBoundParameters.Add('Type', 'error') + } + Write-LogIssue @PSBoundParameters +} + +function Write-PipelineSetVariable { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [string]$Value, + [switch]$Secret, + [switch]$AsOutput, + [bool]$IsMultiJobVariable = $true) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ + 'variable' = $Name + 'isSecret' = $Secret + 'isOutput' = $IsMultiJobVariable + } -AsOutput:$AsOutput + } +} + +function Write-PipelinePrependPath { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput + } +} + +function Write-PipelineSetResult { + [CmdletBinding()] + param( + [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] + [Parameter(Mandatory = $true)] + [string]$Result, + [string]$Message) + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ + 'result' = $Result + } + } +} + +<######################################## +# Private functions. +########################################> +function Format-LoggingCommandData { + [CmdletBinding()] + param([string]$Value, [switch]$Reverse) + + if (!$Value) { + return '' + } + + if (!$Reverse) { + foreach ($mapping in $script:loggingCommandEscapeMappings) { + $Value = $Value.Replace($mapping.Token, $mapping.Replacement) + } + } + else { + for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { + $mapping = $script:loggingCommandEscapeMappings[$i] + $Value = $Value.Replace($mapping.Replacement, $mapping.Token) + } + } + + return $Value +} + +function Format-LoggingCommand { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Area, + [Parameter(Mandatory = $true)] + [string]$Event, + [string]$Data, + [hashtable]$Properties) + + # Append the preamble. + [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder + $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) + + # Append the properties. + if ($Properties) { + $first = $true + foreach ($key in $Properties.Keys) { + [string]$value = Format-LoggingCommandData $Properties[$key] + if ($value) { + if ($first) { + $null = $sb.Append(' ') + $first = $false + } + else { + $null = $sb.Append(';') + } + + $null = $sb.Append("$key=$value") + } + } + } + + # Append the tail and output the value. + $Data = Format-LoggingCommandData $Data + $sb.Append(']').Append($Data).ToString() +} + +function Write-LoggingCommand { + [CmdletBinding(DefaultParameterSetName = 'Parameters')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Area, + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Event, + [Parameter(ParameterSetName = 'Parameters')] + [string]$Data, + [Parameter(ParameterSetName = 'Parameters')] + [hashtable]$Properties, + [Parameter(Mandatory = $true, ParameterSetName = 'Object')] + $Command, + [switch]$AsOutput) + + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput + return + } + + $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties + if ($AsOutput) { + $command + } + else { + Write-Host $command + } +} + +function Write-LogIssue { + [CmdletBinding()] + param( + [ValidateSet('warning', 'error')] + [Parameter(Mandatory = $true)] + [string]$Type, + [string]$Message, + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ + 'type' = $Type + 'code' = $ErrCode + 'sourcepath' = $SourcePath + 'linenumber' = $LineNumber + 'columnnumber' = $ColumnNumber + } + if ($AsOutput) { + return $command + } + + if ($Type -eq 'error') { + $foregroundColor = $host.PrivateData.ErrorForegroundColor + $backgroundColor = $host.PrivateData.ErrorBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Red + $backgroundColor = [System.ConsoleColor]::Black + } + } + else { + $foregroundColor = $host.PrivateData.WarningForegroundColor + $backgroundColor = $host.PrivateData.WarningBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Yellow + $backgroundColor = [System.ConsoleColor]::Black + } + } + + Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor +} diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh new file mode 100644 index 000000000..6a0b2255e --- /dev/null +++ b/eng/common/pipeline-logging-functions.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash + +function Write-PipelineTelemetryError { + local telemetry_category='' + local force=false + local function_args=() + local message='' + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -category|-c) + telemetry_category=$2 + shift + ;; + -force|-f) + force=true + ;; + -*) + function_args+=("$1 $2") + shift + ;; + *) + message=$* + ;; + esac + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$message" >&2 + return + fi + + if [[ $force == true ]]; then + function_args+=("-force") + fi + message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" + function_args+=("$message") + Write-PipelineTaskError ${function_args[@]} +} + +function Write-PipelineTaskError { + local message_type="error" + local sourcepath='' + local linenumber='' + local columnnumber='' + local error_code='' + local force=false + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -type|-t) + message_type=$2 + shift + ;; + -sourcepath|-s) + sourcepath=$2 + shift + ;; + -linenumber|-ln) + linenumber=$2 + shift + ;; + -columnnumber|-cn) + columnnumber=$2 + shift + ;; + -errcode|-e) + error_code=$2 + shift + ;; + -force|-f) + force=true + ;; + *) + break + ;; + esac + + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$@" >&2 + return + fi + + local message="##vso[task.logissue" + + message="$message type=$message_type" + + if [ -n "$sourcepath" ]; then + message="$message;sourcepath=$sourcepath" + fi + + if [ -n "$linenumber" ]; then + message="$message;linenumber=$linenumber" + fi + + if [ -n "$columnnumber" ]; then + message="$message;columnnumber=$columnnumber" + fi + + if [ -n "$error_code" ]; then + message="$message;code=$error_code" + fi + + message="$message]$*" + echo "$message" +} + +function Write-PipelineSetVariable { + if [[ "$ci" != true ]]; then + return + fi + + local name='' + local value='' + local secret=false + local as_output=false + local is_multi_job_variable=true + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -name|-n) + name=$2 + shift + ;; + -value|-v) + value=$2 + shift + ;; + -secret|-s) + secret=true + ;; + -as_output|-a) + as_output=true + ;; + -is_multi_job_variable|-i) + is_multi_job_variable=$2 + shift + ;; + esac + shift + done + + value=${value/;/%3B} + value=${value/\\r/%0D} + value=${value/\\n/%0A} + value=${value/]/%5D} + + local message="##vso[task.setvariable variable=$name;isSecret=$secret;isOutput=$is_multi_job_variable]$value" + + if [[ "$as_output" == true ]]; then + $message + else + echo "$message" + fi +} + +function Write-PipelinePrependPath { + local prepend_path='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -path|-p) + prepend_path=$2 + shift + ;; + esac + shift + done + + export PATH="$prepend_path:$PATH" + + if [[ "$ci" == true ]]; then + echo "##vso[task.prependpath]$prepend_path" + fi +} + +function Write-PipelineSetResult { + local result='' + local message='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -result|-r) + result=$2 + shift + ;; + -message|-m) + message=$2 + shift + ;; + esac + shift + done + + if [[ "$ci" == true ]]; then + echo "##vso[task.complete result=$result;]$message" + fi +} diff --git a/eng/common/post-build/add-build-to-channel.ps1 b/eng/common/post-build/add-build-to-channel.ps1 new file mode 100644 index 000000000..49938f0c8 --- /dev/null +++ b/eng/common/post-build/add-build-to-channel.ps1 @@ -0,0 +1,48 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Check that the channel we are going to promote the build to exist + $channelInfo = Get-MaestroChannel -ChannelId $ChannelId + + if (!$channelInfo) { + Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" + ExitWithExitCode 1 + } + + # Get info about which channel(s) the build has already been promoted to + $buildInfo = Get-MaestroBuild -BuildId $BuildId + + if (!$buildInfo) { + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" + ExitWithExitCode 1 + } + + # Find whether the build is already assigned to the channel or not + if ($buildInfo.channels) { + foreach ($channel in $buildInfo.channels) { + if ($channel.Id -eq $ChannelId) { + Write-Host "The build with BAR ID $BuildId is already on channel $ChannelId!" + ExitWithExitCode 0 + } + } + } + + Write-Host "Promoting build '$BuildId' to channel '$ChannelId'." + + Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/check-channel-consistency.ps1 b/eng/common/post-build/check-channel-consistency.ps1 new file mode 100644 index 000000000..63f3464c9 --- /dev/null +++ b/eng/common/post-build/check-channel-consistency.ps1 @@ -0,0 +1,40 @@ +param( + [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to + [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + if ($PromoteToChannels -eq "") { + Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." + ExitWithExitCode 0 + } + + # Check that every channel that Maestro told to promote the build to + # is available in YAML + $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } + + $hasErrors = $false + + foreach ($id in $PromoteToChannelsIds) { + if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { + Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." + $hasErrors = $true + } + } + + # The `Write-PipelineTaskError` doesn't error the script and we might report several errors + # in the previous lines. The check below makes sure that we return an error state from the + # script if we reported any validation error + if ($hasErrors) { + ExitWithExitCode 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/nuget-validation.ps1 b/eng/common/post-build/nuget-validation.ps1 new file mode 100644 index 000000000..dab3534ab --- /dev/null +++ b/eng/common/post-build/nuget-validation.ps1 @@ -0,0 +1,24 @@ +# This script validates NuGet package metadata information using this +# tool: https://github.com/NuGet/NuGetGallery/tree/jver-verify/src/VerifyMicrosoftPackage + +param( + [Parameter(Mandatory=$true)][string] $PackagesPath, # Path to where the packages to be validated are + [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' + + New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force + + Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 + + & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/post-build-utils.ps1 b/eng/common/post-build/post-build-utils.ps1 new file mode 100644 index 000000000..534f6988d --- /dev/null +++ b/eng/common/post-build/post-build-utils.ps1 @@ -0,0 +1,91 @@ +# Most of the functions in this file require the variables `MaestroApiEndPoint`, +# `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +# `tools.ps1` checks $ci to perform some actions. Since the post-build +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +$disableConfigureToolsetImport = $true +. $PSScriptRoot\..\tools.ps1 + +function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { + Validate-MaestroVars + + $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $headers.Add('Accept', $ContentType) + $headers.Add('Authorization',"Bearer $MaestroApiAccessToken") + return $headers +} + +function Get-MaestroChannel([int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}?api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroBuild([int]$BuildId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/builds/${BuildId}?api-version=$MaestroApiVersion" + + $result = try { return Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { + Validate-MaestroVars + + $SourceRepository = [System.Web.HttpUtility]::UrlEncode($SourceRepository) + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions?sourceRepository=$SourceRepository&channelId=$ChannelId&api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null +} + +function Trigger-Subscription([string]$SubscriptionId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null +} + +function Validate-MaestroVars { + try { + Get-Variable MaestroApiEndPoint | Out-Null + Get-Variable MaestroApiVersion | Out-Null + Get-Variable MaestroApiAccessToken | Out-Null + + if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + ExitWithExitCode 1 + } + + if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + ExitWithExitCode 1 + } + } + catch { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' + Write-Host $_ + ExitWithExitCode 1 + } +} diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1 new file mode 100644 index 000000000..238945cb5 --- /dev/null +++ b/eng/common/post-build/publish-using-darc.ps1 @@ -0,0 +1,53 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, + [Parameter(Mandatory=$true)][string] $AzdoToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', + [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, + [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $darc = Get-Darc + + $optionalParams = [System.Collections.ArrayList]::new() + + if ("" -ne $ArtifactsPublishingAdditionalParameters) { + $optionalParams.Add("--artifact-publishing-parameters") | Out-Null + $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null + } + + if ("" -ne $SymbolPublishingAdditionalParameters) { + $optionalParams.Add("--symbol-publishing-parameters") | Out-Null + $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null + } + + if ("false" -eq $WaitPublishingFinish) { + $optionalParams.Add("--no-wait") | Out-Null + } + + & $darc add-build-to-channel ` + --id $buildId ` + --publishing-infra-version $PublishingInfraVersion ` + --default-channels ` + --source-branch main ` + --azdev-pat "$AzdoToken" ` + --bar-uri "$MaestroApiEndPoint" ` + --ci ` + @optionalParams + + if ($LastExitCode -ne 0) { + Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." + exit 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 new file mode 100644 index 000000000..4011d324e --- /dev/null +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -0,0 +1,319 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade + [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages + [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use +) + +. $PSScriptRoot\post-build-utils.ps1 + +# Cache/HashMap (File -> Exist flag) used to consult whether a file exist +# in the repository at a specific commit point. This is populated by inserting +# all files present in the repo at a specific commit point. +$global:RepoFiles = @{} + +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +$MaxRetries = 5 +$RetryWaitTimeInSeconds = 30 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +if (!$InputPath -or !(Test-Path $InputPath)){ + Write-Host "No files to validate." + ExitWithExitCode 0 +} + +$ValidatePackage = { + param( + [string] $PackagePath # Full path to a Symbols.NuGet package + ) + + . $using:PSScriptRoot\..\tools.ps1 + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-Host "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for SourceLink information + # For now we'll only care about Portable & Embedded PDBs + $RelevantExtensions = @('.dll', '.exe', '.pdb') + + Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + $FailedFiles = 0 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $FileName = $_.FullName + $Extension = [System.IO.Path]::GetExtension($_.Name) + $FakeName = -Join((New-Guid), $Extension) + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName + + # We ignore resource DLLs + if ($FileName.EndsWith('.resources.dll')) { + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + + $ValidateFile = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $RealPath, + [ref] $FailedFiles + ) + + $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools" + $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe" + $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String + + if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { + $NumFailedLinks = 0 + + # We only care about Http addresses + $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches + + if ($Matches.Count -ne 0) { + $Matches.Value | + ForEach-Object { + $Link = $_ + $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/" + + $FilePath = $Link.Replace($CommitUrl, "") + $Status = 200 + $Cache = $using:RepoFiles + + $attempts = 0 + + while ($attempts -lt $using:MaxRetries) { + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + if ($Link -match "submodules") { + # Skip submodule links until sourcelink properly handles submodules + $Status = 200 + } + elseif ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + # Only GitHub links are valid + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + # If it's not a github link, we want to break out of the loop and not retry. + $Status = 0 + $attempts = $using:MaxRetries + } + } + catch { + Write-Host $_ + $Status = 0 + } + } + + if ($Status -ne 200) { + $attempts++ + + if ($attempts -lt $using:MaxRetries) + { + $attemptsLeft = $using:MaxRetries - $attempts + Write-Warning "Download failed, $attemptsLeft attempts remaining, will retry in $using:RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $using:RetryWaitTimeInSeconds + } + else { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ + } + } + else { + break + } + } + } + } + + if ($NumFailedLinks -ne 0) { + $FailedFiles.value++ + $global:LASTEXITCODE = 1 + } + } + } + + &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) + } + } + catch { + Write-Host $_ + } + finally { + $zip.Dispose() + } + + if ($FailedFiles -eq 0) { + Write-Host 'Passed.' + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + else { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$ValidationFailures, + [switch]$logErrors) { + if ($result -ne '0') { + if ($logErrors) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links." + } + $ValidationFailures.Value++ + } +} + +function ValidateSourceLinkLinks { + if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { + if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" + ExitWithExitCode 1 + } + else { + $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2'; + } + } + + if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + ExitWithExitCode 1 + } + + if ($GHRepoName -ne '' -and $GHCommit -ne '') { + $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') + $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') + + try { + # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash + $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree + + foreach ($file in $Data) { + $Extension = [System.IO.Path]::GetExtension($file.path) + + if ($CodeExtensions.Contains($Extension)) { + $RepoFiles[$file.path] = 1 + } + } + } + catch { + Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." + } + } + elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { + Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' + } + + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $ValidationFailures = 0 + + # Process each NuGet package in parallel + Get-ChildItem "$InputPath\*.symbols.nupkg" | + ForEach-Object { + Write-Host "Starting $($_.FullName)" + Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors + Remove-Job -Id $Job.Id + } + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) + Remove-Job -Id $Job.Id + } + if ($ValidationFailures -gt 0) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." + ExitWithExitCode 1 + } +} + +function InstallSourcelinkCli { + $sourcelinkCliPackageName = 'sourcelink' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) { + Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed." + } + else { + Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global + } +} + +try { + InstallSourcelinkCli + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + ValidateSourceLinkLinks +} +catch { + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 new file mode 100644 index 000000000..cd2181baf --- /dev/null +++ b/eng/common/post-build/symbols-validation.ps1 @@ -0,0 +1,339 @@ +param( + [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use + [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs + [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error + [Parameter(Mandatory = $false)][switch] $Clean, # Clean extracted symbols directory after checking symbols + [Parameter(Mandatory = $false)][string] $SymbolExclusionFile # Exclude the symbols in the file from publishing to symbol server +) + +. $PSScriptRoot\..\tools.ps1 +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +# Max number of retries +$MaxRetry = 5 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +# Set error codes +Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 +Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 + +$WindowsPdbVerificationParam = "" +if ($CheckForWindowsPdbs) { + $WindowsPdbVerificationParam = "--windows-pdbs" +} + +$ExclusionSet = New-Object System.Collections.Generic.HashSet[string]; + +if (!$InputPath -or !(Test-Path $InputPath)){ + Write-Host "No symbols to validate." + ExitWithExitCode 0 +} + +#Check if the path exists +if ($SymbolExclusionFile -and (Test-Path $SymbolExclusionFile)){ + [string[]]$Exclusions = Get-Content "$SymbolExclusionFile" + $Exclusions | foreach { if($_ -and $_.Trim()){$ExclusionSet.Add($_)} } +} +else{ + Write-Host "Symbol Exclusion file does not exists. No symbols to exclude." +} + +$CountMissingSymbols = { + param( + [string] $PackagePath, # Path to a NuGet package + [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs + ) + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + Write-Host "Validating $PackagePath " + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = $using:ERROR_FILEDOESNOTEXIST + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for symbols + $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') + + # How many files are missing symbol information + $MissingSymbols = 0 + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $PackageGuid = New-Guid + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' + + try { + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + } + catch { + Write-Host "Something went wrong extracting $PackagePath" + Write-Host $_ + return [pscustomobject]@{ + result = $using:ERROR_BADEXTRACT + packagePath = $PackagePath + } + } + + Get-ChildItem -Recurse $ExtractPath | + Where-Object { $RelevantExtensions -contains $_.Extension } | + ForEach-Object { + $FileName = $_.FullName + if ($FileName -Match '\\ref\\') { + Write-Host "`t Ignoring reference assembly file " $FileName + return + } + + $FirstMatchingSymbolDescriptionOrDefault = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. + [string] $SymbolsPath + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + '\' + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + + $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" + $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" + + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetry) { + + # Save the output and get diagnostic output + $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String + + if ((Test-Path $PdbPath) -and (Test-path $SymbolPath)) { + return 'Module and PDB for Module' + } + elseif ((Test-Path $NGenPdb) -and (Test-Path $PdbPath) -and (Test-Path $SymbolPath)) { + return 'Dll, PDB and NGen PDB' + } + elseif ((Test-Path $SODbg) -and (Test-Path $SymbolPath)) { + return 'So and DBG for SO' + } + elseif ((Test-Path $DylibDwarf) -and (Test-Path $SymbolPath)) { + return 'Dylib and Dwarf for Dylib' + } + elseif (Test-Path $SymbolPath) { + return 'Module' + } + else + { + $totalRetries++ + } + } + + return $null + } + + $FileRelativePath = $FileName.Replace("$ExtractPath\", "") + if (($($using:ExclusionSet) -ne $null) -and ($($using:ExclusionSet).Contains($FileRelativePath) -or ($($using:ExclusionSet).Contains($FileRelativePath.Replace("\", "/"))))){ + Write-Host "Skipping $FileName from symbol validation" + } + + else { + $FileGuid = New-Guid + $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid + + $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--microsoft-symbol-server' ` + -SymbolsPath "$ExpandedSymbolsPath-msdl" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--internal-server' ` + -SymbolsPath "$ExpandedSymbolsPath-symweb" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + + Write-Host -NoNewLine "`t Checking file " $FileName "... " + + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host 'No symbols found on MSDL or SymWeb!' + } + else { + if ($SymbolsOnMSDL -eq $null) { + Write-Host 'No symbols found on MSDL!' + } + else { + Write-Host 'No symbols found on SymWeb!' + } + } + } + } + } + + if ($using:Clean) { + Remove-Item $ExtractPath -Recurse -Force + } + + Pop-Location + + return [pscustomobject]@{ + result = $MissingSymbols + packagePath = $PackagePath + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$DupedSymbols, + [ref]$TotalFailures) { + if ($result -eq $ERROR_BADEXTRACT) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" + $DupedSymbols.Value++ + } + elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" + $TotalFailures.Value++ + } + elseif ($result -gt '0') { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" + $TotalFailures.Value++ + } + else { + Write-Host "All symbols verified for package $packagePath" + } +} + +function CheckSymbolsAvailable { + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $TotalPackages = 0 + $TotalFailures = 0 + $DupedSymbols = 0 + + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $FileName = $_.Name + $FullName = $_.FullName + + # These packages from Arcade-Services include some native libraries that + # our current symbol uploader can't handle. Below is a workaround until + # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. + if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + + $TotalPackages++ + + Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null + + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + Remove-Job -Id $Job.Id + } + Write-Host + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + } + + if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { + if ($TotalFailures -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" + } + + if ($DupedSymbols -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" + } + + ExitWithExitCode 1 + } + else { + Write-Host "All symbols validated!" + } +} + +function InstallDotnetSymbol { + $dotnetSymbolPackageName = 'dotnet-symbol' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$dotnetSymbolPackageName*") -and ($toolList -like "*$dotnetSymbolVersion*")) { + Write-Host "dotnet-symbol version $dotnetSymbolVersion is already installed." + } + else { + Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global + } +} + +try { + . $PSScriptRoot\post-build-utils.ps1 + + InstallDotnetSymbol + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + CheckSymbolsAvailable +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 new file mode 100644 index 000000000..ac9a95778 --- /dev/null +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -0,0 +1,64 @@ +param( + [Parameter(Mandatory=$true)][string] $SourceRepo, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Get all the $SourceRepo subscriptions + $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') + $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId + + if (!$subscriptions) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 + } + + $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] + $failedTriggeredSubscription = $false + + # Get all enabled subscriptions that need dependency flow on 'everyBuild' + foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: ${$subscription.id}" + [void]$subscriptionsToTrigger.Add($subscription.id) + } + } + + foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host 'done.' + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true + } + } + + if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + } + elseif ($failedTriggeredSubscription) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' + ExitWithExitCode 1 + } + else { + Write-Host 'All subscriptions were triggered successfully!' + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/retain-build.ps1 b/eng/common/retain-build.ps1 new file mode 100644 index 000000000..e7ba975ad --- /dev/null +++ b/eng/common/retain-build.ps1 @@ -0,0 +1,45 @@ + +Param( +[Parameter(Mandatory=$true)][int] $buildId, +[Parameter(Mandatory=$true)][string] $azdoOrgUri, +[Parameter(Mandatory=$true)][string] $azdoProject, +[Parameter(Mandatory=$true)][string] $token +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +function Get-AzDOHeaders( + [string] $token) +{ + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":${token}")) + $headers = @{"Authorization"="Basic $base64AuthInfo"} + return $headers +} + +function Update-BuildRetention( + [string] $azdoOrgUri, + [string] $azdoProject, + [int] $buildId, + [string] $token) +{ + $headers = Get-AzDOHeaders -token $token + $requestBody = "{ + `"keepForever`": `"true`" + }" + + $requestUri = "${azdoOrgUri}/${azdoProject}/_apis/build/builds/${buildId}?api-version=6.0" + write-Host "Attempting to retain build using the following URI: ${requestUri} ..." + + try { + Invoke-RestMethod -Uri $requestUri -Method Patch -Body $requestBody -Header $headers -contentType "application/json" + Write-Host "Updated retention settings for build ${buildId}." + } + catch { + Write-Error "Failed to update retention settings for build: $_.Exception.Response.StatusDescription" + exit 1 + } +} + +Update-BuildRetention -azdoOrgUri $azdoOrgUri -azdoProject $azdoProject -buildId $buildId -token $token +exit 0 diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 new file mode 100644 index 000000000..4f0546dce --- /dev/null +++ b/eng/common/sdk-task.ps1 @@ -0,0 +1,97 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $configuration = 'Debug', + [string] $task, + [string] $verbosity = 'minimal', + [string] $msbuildEngine = $null, + [switch] $restore, + [switch] $prepareMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +$ci = $true +$binaryLog = $true +$warnAsError = $true + +. $PSScriptRoot\tools.ps1 + +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + Write-Host " -restore Restore dependencies" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -prepareMachine Prepare machine for CI run" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + Write-Host "Command line arguments not listed above are passed thru to msbuild." +} + +function Build([string]$target) { + $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } + $log = Join-Path $LogDir "$task$logSuffix.binlog" + $outputPath = Join-Path $ToolsetDir "$task\" + + MSBuild $taskProject ` + /bl:$log ` + /t:$target ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:BaseIntermediateOutputPath=$outputPath ` + /v:$verbosity ` + @properties +} + +try { + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($task -eq "") { + Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" + Print-Usage + ExitWithExitCode 1 + } + + if( $msbuildEngine -eq "vs") { + # Ensure desktop MSBuild is available for sdk tasks. + if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { + $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty + } + if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.12.0" -MemberType NoteProperty + } + if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { + $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true + } + if ($xcopyMSBuildToolsFolder -eq $null) { + throw 'Unable to get xcopy downloadable version of msbuild' + } + + $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" + } + + $taskProject = GetSdkTaskProject $task + if (!(Test-Path $taskProject)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" + ExitWithExitCode 1 + } + + if ($restore) { + Build 'Restore' + } + + Build 'Execute' +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/sdl/NuGet.config b/eng/common/sdl/NuGet.config new file mode 100644 index 000000000..5bfbb02ef --- /dev/null +++ b/eng/common/sdl/NuGet.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/eng/common/sdl/configure-sdl-tool.ps1 b/eng/common/sdl/configure-sdl-tool.ps1 new file mode 100644 index 000000000..27f5a4115 --- /dev/null +++ b/eng/common/sdl/configure-sdl-tool.ps1 @@ -0,0 +1,130 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $TargetDirectory, + [string] $GdnFolder, + # The list of Guardian tools to configure. For each object in the array: + # - If the item is a [hashtable], it must contain these entries: + # - Name = The tool name as Guardian knows it. + # - Scenario = (Optional) Scenario-specific name for this configuration entry. It must be unique + # among all tool entries with the same Name. + # - Args = (Optional) Array of Guardian tool configuration args, like '@("Target > C:\temp")' + # - If the item is a [string] $v, it is treated as '@{ Name="$v" }' + [object[]] $ToolsList, + [string] $GuardianLoggerLevel='Standard', + # Optional: Additional params to add to any tool using CredScan. + [string[]] $CrScanAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using PoliCheck. + [string[]] $PoliCheckAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using CodeQL/Semmle. + [string[]] $CodeQLAdditionalRunConfigParams, + # Optional: Additional params to add to any tool using Binskim. + [string[]] $BinskimAdditionalRunConfigParams +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # Normalize tools list: all in [hashtable] form with defined values for each key. + $ToolsList = $ToolsList | + ForEach-Object { + if ($_ -is [string]) { + $_ = @{ Name = $_ } + } + + if (-not ($_['Scenario'])) { $_.Scenario = "" } + if (-not ($_['Args'])) { $_.Args = @() } + $_ + } + + Write-Host "List of tools to configure:" + $ToolsList | ForEach-Object { $_ | Out-String | Write-Host } + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + foreach ($tool in $ToolsList) { + # Put together the name and scenario to make a unique key. + $toolConfigName = $tool.Name + if ($tool.Scenario) { + $toolConfigName += "_" + $tool.Scenario + } + + Write-Host "=== Configuring $toolConfigName..." + + $gdnConfigFile = Join-Path $gdnConfigPath "$toolConfigName-configure.gdnconfig" + + # For some tools, add default and automatic args. + switch -Exact ($tool.Name) { + 'credscan' { + if ($targetDirectory) { + $tool.Args += "`"TargetDirectory < $TargetDirectory`"" + } + $tool.Args += "`"OutputType < pre`"" + $tool.Args += $CrScanAdditionalRunConfigParams + } + 'policheck' { + if ($targetDirectory) { + $tool.Args += "`"Target < $TargetDirectory`"" + } + $tool.Args += $PoliCheckAdditionalRunConfigParams + } + {$_ -in 'semmle', 'codeql'} { + if ($targetDirectory) { + $tool.Args += "`"SourceCodeDirectory < $TargetDirectory`"" + } + $tool.Args += $CodeQLAdditionalRunConfigParams + } + 'binskim' { + if ($targetDirectory) { + # Binskim crashes due to specific PDBs. GitHub issue: https://github.com/microsoft/binskim/issues/924. + # We are excluding all `_.pdb` files from the scan. + $tool.Args += "`"Target < $TargetDirectory\**;-:file|$TargetDirectory\**\_.pdb`"" + } + $tool.Args += $BinskimAdditionalRunConfigParams + } + } + + # Create variable pointing to the args array directly so we can use splat syntax later. + $toolArgs = $tool.Args + + # Configure the tool. If args array is provided or the current tool has some default arguments + # defined, add "--args" and splat each element on the end. Arg format is "{Arg id} < {Value}", + # one per parameter. Doc page for "guardian configure": + # https://dev.azure.com/securitytools/SecurityIntegration/_wiki/wikis/Guardian/1395/configure + Exec-BlockVerbosely { + & $GuardianCliLocation configure ` + --working-directory $WorkingDirectory ` + --tool $tool.Name ` + --output-path $gdnConfigFile ` + --logger-level $GuardianLoggerLevel ` + --noninteractive ` + --force ` + $(if ($toolArgs) { "--args" }) @toolArgs + Exit-IfNZEC "Sdl" + } + + Write-Host "Created '$toolConfigName' configuration file: $gdnConfigFile" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 new file mode 100644 index 000000000..81ded5b7f --- /dev/null +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -0,0 +1,165 @@ +Param( + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + + # Optional: list of SDL tools to run on source code. See 'configure-sdl-tool.ps1' for tools list + # format. + [object[]] $SourceToolsList, + # Optional: list of SDL tools to run on built artifacts. See 'configure-sdl-tool.ps1' for tools + # list format. + [object[]] $ArtifactToolsList, + # Optional: list of SDL tools to run without automatically specifying a target directory. See + # 'configure-sdl-tool.ps1' for tools list format. + [object[]] $CustomToolsList, + + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional Params to custom build a Semmle/CodeQL run config in the format @("xyz < abc","sdf < 1") + [string[]] $BinskimAdditionalRunConfigParams, # Optional: Additional Params to custom build a Binskim run config in the format @("xyz < abc","sdf < 1") + [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run +) + +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } + + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } + + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -GuardianLoggerLevel $GuardianLoggerLevel + } + $gdnFolder = Join-Path $workingDirectory '.gdn' + + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Exec-BlockVerbosely { + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 + } + } + + # Configure a list of tools with a default target directory. Populates the ".gdn/r" directory. + function Configure-ToolsList([object[]] $tools, [string] $targetDirectory) { + if ($tools -and $tools.Count -gt 0) { + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'configure-sdl-tool.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $workingDirectory ` + -TargetDirectory $targetDirectory ` + -GdnFolder $gdnFolder ` + -ToolsList $tools ` + -GuardianLoggerLevel $GuardianLoggerLevel ` + -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams ` + -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams ` + -CodeQLAdditionalRunConfigParams $CodeQLAdditionalRunConfigParams ` + -BinskimAdditionalRunConfigParams $BinskimAdditionalRunConfigParams + if ($BreakOnFailure) { + Exit-IfNZEC "Sdl" + } + } + } + } + + # Configure Artifact and Source tools with default Target directories. + Configure-ToolsList $ArtifactToolsList $ArtifactsDirectory + Configure-ToolsList $SourceToolsList $SourceDirectory + # Configure custom tools with no default Target directory. + Configure-ToolsList $CustomToolsList $null + + # At this point, all tools are configured in the ".gdn" directory. Run them all in a single call. + # (If we used "run" multiple times, each run would overwrite data from earlier runs.) + Exec-BlockVerbosely { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') ` + -GuardianCliLocation $guardianCliLocation ` + -WorkingDirectory $SourceDirectory ` + -UpdateBaseline $UpdateBaseline ` + -GdnFolder $gdnFolder + } + + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Exec-BlockVerbosely { + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 + } + } + + if ($BreakOnFailure) { + Write-Host "Failing the build in case of breaking results..." + Exec-BlockVerbosely { + & $guardianCliLocation break --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + } + } else { + Write-Host "Letting the build pass even if there were breaking results..." + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 +} diff --git a/eng/common/sdl/extract-artifact-archives.ps1 b/eng/common/sdl/extract-artifact-archives.ps1 new file mode 100644 index 000000000..68da4fbf2 --- /dev/null +++ b/eng/common/sdl/extract-artifact-archives.ps1 @@ -0,0 +1,63 @@ +# This script looks for each archive file in a directory and extracts it into the target directory. +# For example, the file "$InputPath/bin.tar.gz" extracts to "$ExtractPath/bin.tar.gz.extracted/**". +# Uses the "tar" utility added to Windows 10 / Windows 2019 that supports tar.gz and zip. +param( + # Full path to directory where archives are stored. + [Parameter(Mandatory=$true)][string] $InputPath, + # Full path to directory to extract archives into. May be the same as $InputPath. + [Parameter(Mandatory=$true)][string] $ExtractPath +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + Measure-Command { + $jobs = @() + + # Find archive files for non-Windows and Windows builds. + $archiveFiles = @( + Get-ChildItem (Join-Path $InputPath "*.tar.gz") + Get-ChildItem (Join-Path $InputPath "*.zip") + ) + + foreach ($targzFile in $archiveFiles) { + $jobs += Start-Job -ScriptBlock { + $file = $using:targzFile + $fileName = [System.IO.Path]::GetFileName($file) + $extractDir = Join-Path $using:ExtractPath "$fileName.extracted" + + New-Item $extractDir -ItemType Directory -Force | Out-Null + + Write-Host "Extracting '$file' to '$extractDir'..." + + # Pipe errors to stdout to prevent PowerShell detecting them and quitting the job early. + # This type of quit skips the catch, so we wouldn't be able to tell which file triggered the + # error. Save output so it can be stored in the exception string along with context. + $output = tar -xf $file -C $extractDir 2>&1 + # Handle NZEC manually rather than using Exit-IfNZEC: we are in a background job, so we + # don't have access to the outer scope. + if ($LASTEXITCODE -ne 0) { + throw "Error extracting '$file': non-zero exit code ($LASTEXITCODE). Output: '$output'" + } + + Write-Host "Extracted to $extractDir" + } + } + + Receive-Job $jobs -Wait + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 new file mode 100644 index 000000000..f031ed5b2 --- /dev/null +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -0,0 +1,82 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored + [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +function ExtractArtifacts { + if (!(Test-Path $InputPath)) { + Write-Host "Input Path does not exist: $InputPath" + ExitWithExitCode 0 + } + $Jobs = @() + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetPath = Join-Path -Path $ExtractPath -ChildPath (Split-Path -Path $_.FullName) + [System.IO.Directory]::CreateDirectory($TargetPath); + + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.FullName + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile) + } + } + catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } + Measure-Command { ExtractArtifacts } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 new file mode 100644 index 000000000..588ff8e22 --- /dev/null +++ b/eng/common/sdl/init-sdl.ps1 @@ -0,0 +1,47 @@ +Param( + [string] $GuardianCliLocation, + [string] $Repository, + [string] $BranchName='master', + [string] $WorkingDirectory, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +# Don't display the console progress UI - it's a huge perf hit +$ProgressPreference = 'SilentlyContinue' + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +try { + # if the folder does not exist, we'll do a guardian init and push it to the remote repository + Write-Host 'Initializing Guardian...' + Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" + & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + # We create the mainbaseline so it can be edited later + Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" + & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + ExitWithExitCode 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config new file mode 100644 index 000000000..e5f543ea6 --- /dev/null +++ b/eng/common/sdl/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 new file mode 100644 index 000000000..2eac8c78f --- /dev/null +++ b/eng/common/sdl/run-sdl.ps1 @@ -0,0 +1,49 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $GdnFolder, + [string] $UpdateBaseline, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # We store config files in the r directory of .gdn + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + $gdnConfigFiles = Get-ChildItem $gdnConfigPath -Recurse -Include '*.gdnconfig' + Write-Host "Discovered Guardian config files:" + $gdnConfigFiles | Out-String | Write-Host + + Exec-BlockVerbosely { + & $GuardianCliLocation run ` + --working-directory $WorkingDirectory ` + --baseline mainbaseline ` + --update-baseline $UpdateBaseline ` + --logger-level $GuardianLoggerLevel ` + --config @gdnConfigFiles + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/sdl.ps1 b/eng/common/sdl/sdl.ps1 new file mode 100644 index 000000000..7fe603fe9 --- /dev/null +++ b/eng/common/sdl/sdl.ps1 @@ -0,0 +1,40 @@ + +function Install-Gdn { + param( + [Parameter(Mandatory=$true)] + [string]$Path, + + [string]$Source = "https://pkgs.dev.azure.com/dnceng/_packaging/Guardian1ESPTUpstreamOrgFeed/nuget/v3/index.json", + + # If omitted, install the latest version of Guardian, otherwise install that specific version. + [string]$Version + ) + + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $global:LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $argumentList = @("install", "Microsoft.Guardian.Cli.win-x64", "-Source $Source", "-OutputDirectory $Path", "-NonInteractive", "-NoCache") + + if ($Version) { + $argumentList += "-Version $Version" + } + + Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait + + $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path + + if (!$gdnCliPath) + { + Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian' + } + + return $gdnCliPath.FullName +} \ No newline at end of file diff --git a/eng/common/sdl/trim-assets-version.ps1 b/eng/common/sdl/trim-assets-version.ps1 new file mode 100644 index 000000000..a2e004877 --- /dev/null +++ b/eng/common/sdl/trim-assets-version.ps1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS +Install and run the 'Microsoft.DotNet.VersionTools.Cli' tool with the 'trim-artifacts-version' command to trim the version from the NuGet assets file name. + +.PARAMETER InputPath +Full path to directory where artifact packages are stored + +.PARAMETER Recursive +Search for NuGet packages recursively + +#> + +Param( + [string] $InputPath, + [bool] $Recursive = $true +) + +$CliToolName = "Microsoft.DotNet.VersionTools.Cli" + +function Install-VersionTools-Cli { + param( + [Parameter(Mandatory=$true)][string]$Version + ) + + Write-Host "Installing the package '$CliToolName' with a version of '$version' ..." + $feed = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" + + $argumentList = @("tool", "install", "--local", "$CliToolName", "--add-source $feed", "--no-cache", "--version $Version", "--create-manifest-if-needed") + Start-Process "$dotnet" -Verbose -ArgumentList $argumentList -NoNewWindow -Wait +} + +# ------------------------------------------------------------------- + +if (!(Test-Path $InputPath)) { + Write-Host "Input Path '$InputPath' does not exist" + ExitWithExitCode 1 +} + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true +$global:LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +try { + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + + $toolsetVersion = Read-ArcadeSdkVersion + Install-VersionTools-Cli -Version $toolsetVersion + + $cliToolFound = (& "$dotnet" tool list --local | Where-Object {$_.Split(' ')[0] -eq $CliToolName}) + if ($null -eq $cliToolFound) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "The '$CliToolName' tool is not installed." + ExitWithExitCode 1 + } + + Exec-BlockVerbosely { + & "$dotnet" $CliToolName trim-assets-version ` + --assets-path $InputPath ` + --recursive $Recursive + Exit-IfNZEC "Sdl" + } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml new file mode 100644 index 000000000..98ccbd7a9 --- /dev/null +++ b/eng/common/templates-official/job/job.yml @@ -0,0 +1,265 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + templateContext: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + enableBuildRetry: false + disableComponentGovernance: '' + componentGovernanceIgnoreDirectories: '' + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle template variable syntax + # example: + # - template: path/to/template.yml + # parameters: + # [key]: [value] + - ${{ if ne(variable.template, '') }}: + - template: ${{ variable.template }} + ${{ if ne(variable.parameters, '') }}: + parameters: ${{ variable.parameters }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: + - task: NuGetAuthenticate@1 + + - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} + continueOnError: true + + - template: /eng/common/templates-official/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: 'artifacts/log' + artifactName: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: 'Publish logs' + continueOnError: true + condition: always() + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates-official/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} + IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableBuildRetry, 'true') }}: + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration' + artifactName: 'BuildConfiguration' + displayName: 'Publish build retry configuration' + continueOnError: true diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml new file mode 100644 index 000000000..52b4d05d3 --- /dev/null +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -0,0 +1,112 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: '' + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + ReusePr: true + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + GitHubOrg: dotnet + MirrorRepo: '' + MirrorBranch: main + condition: '' + JobNameSuffix: '' + +jobs: +- job: OneLocBuild${{ parameters.JobNameSuffix }} + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + isShouldReusePrSelected: ${{ parameters.ReusePr }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if ne(parameters.MirrorRepo, '') }}: + isMirrorRepoSelected: true + gitHubOrganization: ${{ parameters.GitHubOrg }} + mirrorRepo: ${{ parameters.MirrorRepo }} + mirrorBranch: ${{ parameters.MirrorBranch }} + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml new file mode 100644 index 000000000..b2ccd9df6 --- /dev/null +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -0,0 +1,160 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishAssetsImmediately: false + + artifactsPublishingAdditionalParameters: '' + + signingValidationAdditionalParameters: '' + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 + + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry + + variables: + - template: /eng/common/templates-official/variables/pool-providers.yml + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + - name: runCodesignValidationInjection + value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/common-variables.yml + + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 + os: windows + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: NuGetAuthenticate@1 + + - task: AzureCLI@2 + displayName: Publish Build Assets + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/sdk-task.ps1 + arguments: > + -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:MaestroApiEndpoint=https://maestro.dot.net + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + New-Item -Path "$(Build.StagingDirectory)/ReleaseConfigs" -ItemType Directory -Force + $filePath = "$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt" + Add-Content -Path $filePath -Value $(BARBuildId) + Add-Content -Path $filePath -Value "$(DefaultChannels)" + Add-Content -Path $filePath -Value $(IsStableBuild) + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish ReleaseConfigs Artifact + inputs: + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - task: powershell@2 + displayName: Check if SymbolPublishingExclusionsFile.txt exists + inputs: + targetType: inline + script: | + $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + if(Test-Path -Path $symbolExclusionfile) + { + Write-Host "SymbolExclusionFile exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]true" + } + else{ + Write-Host "Symbols Exclusion file does not exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]false" + } + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish SymbolPublishingExclusionsFile Artifact + condition: eq(variables['SymbolExclusionFile'], 'true') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion 3 + -AzdoToken '$(System.AccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates-official/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml new file mode 100644 index 000000000..4217d6d8b --- /dev/null +++ b/eng/common/templates-official/job/source-build.yml @@ -0,0 +1,75 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + + # If set to true and running on a non-public project, + # Internal blob storage locations will be enabled. + # This is not enabled by default because many repositories do not need internal sources + # and do not need to have the required service connections approved in the pipeline. + enableInternalSources: false + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates-official/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open + + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] + image: 1es-mariner-2 + os: linux + + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - ${{ if eq(parameters.enableInternalSources, true) }}: + - template: /eng/common/templates-official/steps/enable-internal-runtimes.yml + - template: /eng/common/templates-official/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml new file mode 100644 index 000000000..fb632b71a --- /dev/null +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -0,0 +1,83 @@ +parameters: + runAsPublic: false + sourceIndexUploadPackageVersion: 2.0.0-20250425.2 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250425.2 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + condition: '' + dependsOn: '' + pool: '' + +jobs: +- job: SourceIndexStage1 + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + - name: SourceIndexUploadPackageVersion + value: ${{ parameters.sourceIndexUploadPackageVersion }} + - name: SourceIndexProcessBinlogPackageVersion + value: ${{ parameters.sourceIndexProcessBinlogPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64.open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $(DncEngInternalBuildPool) + image: windows.vs2022.amd64 + os: windows + + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET 8 SDK + inputs: + packageType: sdk + version: 8.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + + - script: | + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(sourceIndexProcessBinlogPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(sourceIndexUploadPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + displayName: Download Tools + # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. + workingDirectory: $(Agent.TempDirectory) + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: AzureCLI@2 + displayName: Get stage 1 auth token + inputs: + azureSubscription: 'SourceDotNet Stage1 Publish' + addSpnToEnvironment: true + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + echo "##vso[task.setvariable variable=ARM_CLIENT_ID;issecret=true]$env:servicePrincipalId" + echo "##vso[task.setvariable variable=ARM_ID_TOKEN;issecret=true]$env:idToken" + echo "##vso[task.setvariable variable=ARM_TENANT_ID;issecret=true]$env:tenantId" + + - script: | + az login --service-principal -u $(ARM_CLIENT_ID) --tenant $(ARM_TENANT_ID) --allow-no-subscriptions --federated-token $(ARM_ID_TOKEN) + displayName: "Login to Azure" + + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 + displayName: Upload stage1 artifacts to source index diff --git a/eng/common/templates-official/jobs/codeql-build.yml b/eng/common/templates-official/jobs/codeql-build.yml new file mode 100644 index 000000000..b68d3c2f3 --- /dev/null +++ b/eng/common/templates-official/jobs/codeql-build.yml @@ -0,0 +1,31 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + +jobs: +- template: /eng/common/templates-official/jobs/jobs.yml + parameters: + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff --git a/eng/common/templates-official/jobs/jobs.yml b/eng/common/templates-official/jobs/jobs.yml new file mode 100644 index 000000000..857a0f8ba --- /dev/null +++ b/eng/common/templates-official/jobs/jobs.yml @@ -0,0 +1,97 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates-official/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. + publishAssetsImmediately: false + + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates-official/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff --git a/eng/common/templates-official/jobs/source-build.yml b/eng/common/templates-official/jobs/source-build.yml new file mode 100644 index 000000000..b9247be15 --- /dev/null +++ b/eng/common/templates-official/jobs/source-build.yml @@ -0,0 +1,54 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates-official/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-9-amd64' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + + # If set to true and running on a non-public project, + # Internal nuget and blob storage locations will be enabled. + # This is not enabled by default because many repositories do not need internal sources + # and do not need to have the required service connections approved in the pipeline. + enableInternalSources: false + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + enableInternalSources: ${{ parameters.enableInternalSources }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} + enableInternalSources: ${{ parameters.enableInternalSources }} diff --git a/eng/common/templates-official/post-build/common-variables.yml b/eng/common/templates-official/post-build/common-variables.yml new file mode 100644 index 000000000..173914f23 --- /dev/null +++ b/eng/common/templates-official/post-build/common-variables.yml @@ -0,0 +1,22 @@ +variables: + - group: Publish-Build-Assets + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro.dot.net" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml new file mode 100644 index 000000000..b81b8770b --- /dev/null +++ b/eng/common/templates-official/post-build/post-build.yml @@ -0,0 +1,287 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + +stages: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: NuGet Validation + condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + BinlogToolVersion: $(BinlogToolVersion) + + - job: + displayName: SourceLink Validation + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + +- ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ else }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: Publish Using Darc + timeoutInMinutes: 120 + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: NuGetAuthenticate@1 + + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -AzdoToken '$(System.AccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates-official/post-build/setup-maestro-vars.yml b/eng/common/templates-official/post-build/setup-maestro-vars.yml new file mode 100644 index 000000000..0c87f149a --- /dev/null +++ b/eng/common/templates-official/post-build/setup-maestro-vars.yml @@ -0,0 +1,70 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +steps: + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + pwsh: true + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates-official/post-build/trigger-subscription.yml b/eng/common/templates-official/post-build/trigger-subscription.yml new file mode 100644 index 000000000..da669030d --- /dev/null +++ b/eng/common/templates-official/post-build/trigger-subscription.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/add-build-to-channel.yml b/eng/common/templates-official/steps/add-build-to-channel.yml new file mode 100644 index 000000000..f67a210d6 --- /dev/null +++ b/eng/common/templates-official/steps/add-build-to-channel.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/build-reason.yml b/eng/common/templates-official/steps/build-reason.yml new file mode 100644 index 000000000..eba58109b --- /dev/null +++ b/eng/common/templates-official/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml new file mode 100644 index 000000000..cbba05967 --- /dev/null +++ b/eng/common/templates-official/steps/component-governance.yml @@ -0,0 +1,13 @@ +parameters: + disableComponentGovernance: false + componentGovernanceIgnoreDirectories: '' + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true + inputs: + ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/enable-internal-runtimes.yml b/eng/common/templates-official/steps/enable-internal-runtimes.yml new file mode 100644 index 000000000..93a8394a6 --- /dev/null +++ b/eng/common/templates-official/steps/enable-internal-runtimes.yml @@ -0,0 +1,28 @@ +# Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' +# variable with the base64-encoded SAS token, by default + +parameters: +- name: federatedServiceConnection + type: string + default: 'dotnetbuilds-internal-read' +- name: outputVariableName + type: string + default: 'dotnetbuilds-internal-container-read-token-base64' +- name: expiryInHours + type: number + default: 1 +- name: base64Encode + type: boolean + default: true + +steps: +- ${{ if ne(variables['System.TeamProject'], 'public') }}: + - template: /eng/common/templates-official/steps/get-delegation-sas.yml + parameters: + federatedServiceConnection: ${{ parameters.federatedServiceConnection }} + outputVariableName: ${{ parameters.outputVariableName }} + expiryInHours: ${{ parameters.expiryInHours }} + base64Encode: ${{ parameters.base64Encode }} + storageAccount: dotnetbuilds + container: internal + permissions: rl diff --git a/eng/common/templates-official/steps/execute-codeql.yml b/eng/common/templates-official/steps/execute-codeql.yml new file mode 100644 index 000000000..9b4a5ffa3 --- /dev/null +++ b/eng/common/templates-official/steps/execute-codeql.yml @@ -0,0 +1,32 @@ +parameters: + # Language that should be analyzed. Defaults to csharp + language: csharp + # Build Commands + buildCommands: '' + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + # optional: determines whether to continue the build if the step errors; + sdlContinueOnError: false + +steps: +- template: /eng/common/templates-official/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: '${{ parameters.additionalParameters }} + -CodeQLAdditionalRunConfigParams @("BuildCommands < ${{ parameters.buildCommands }}", "Language < ${{ parameters.language }}")' + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/execute-sdl.yml b/eng/common/templates-official/steps/execute-sdl.yml new file mode 100644 index 000000000..301d5c591 --- /dev/null +++ b/eng/common/templates-official/steps/execute-sdl.yml @@ -0,0 +1,86 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- task: NuGetAuthenticate@1 + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL (Overridden) + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianCliLocation $(GuardianCliLocation) + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if ne(parameters.publishGuardianDirectoryToPipeline, 'false') }}: + # We want to publish the Guardian results and configuration for easy diagnosis. However, the + # '.gdn' dir is a mix of configuration, results, extracted dependencies, and Guardian default + # tooling files. Some of these files are large and aren't useful during an investigation, so + # exclude them by simply deleting them before publishing. (As of writing, there is no documented + # way to selectively exclude a dir from the pipeline artifact publish task.) + - task: DeleteFiles@1 + displayName: Delete Guardian dependencies to avoid uploading + inputs: + SourceFolder: $(Agent.BuildDirectory)/.gdn + Contents: | + c + i + condition: succeededOrFailed() + + - publish: $(Agent.BuildDirectory)/.gdn + artifact: GuardianConfiguration + displayName: Publish GuardianConfiguration + condition: succeededOrFailed() + + # Publish the SARIF files in a container named CodeAnalysisLogs to enable integration + # with the "SARIF SAST Scans Tab" Azure DevOps extension + - task: CopyFiles@2 + displayName: Copy SARIF files + inputs: + flattenFolders: true + sourceFolder: $(Agent.BuildDirectory)/.gdn/rc/ + contents: '**/*.sarif' + targetFolder: $(Build.SourcesDirectory)/CodeAnalysisLogs + condition: succeededOrFailed() + + # Use PublishBuildArtifacts because the SARIF extension only checks this case + # see microsoft/sarif-azuredevops-extension#4 + - task: PublishBuildArtifacts@1 + displayName: Publish SARIF files to CodeAnalysisLogs container + inputs: + pathToPublish: $(Build.SourcesDirectory)/CodeAnalysisLogs + artifactName: CodeAnalysisLogs + condition: succeededOrFailed() \ No newline at end of file diff --git a/eng/common/templates-official/steps/generate-sbom.yml b/eng/common/templates-official/steps/generate-sbom.yml new file mode 100644 index 000000000..daf0957b6 --- /dev/null +++ b/eng/common/templates-official/steps/generate-sbom.yml @@ -0,0 +1,48 @@ +# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. +# PackageName - The name of the package this SBOM represents. +# PackageVersion - The version of the package this SBOM represents. +# ManifestDirPath - The path of the directory where the generated manifest files will be placed +# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. + +parameters: + PackageVersion: 8.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + IgnoreDirectories: '' + sbomContinueOnError: true + +steps: +- task: PowerShell@2 + displayName: Prep for SBOM generation in (Non-linux) + condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) + inputs: + filePath: ./eng/common/generate-sbom-prep.ps1 + arguments: ${{parameters.manifestDirPath}} + +# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 +- script: | + chmod +x ./eng/common/generate-sbom-prep.sh + ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} + displayName: Prep for SBOM generation in (Linux) + condition: eq(variables['Agent.Os'], 'Linux') + continueOnError: ${{ parameters.sbomContinueOnError }} + +- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM manifest' + continueOnError: ${{ parameters.sbomContinueOnError }} + inputs: + PackageName: ${{ parameters.packageName }} + BuildDropPath: ${{ parameters.buildDropPath }} + PackageVersion: ${{ parameters.packageVersion }} + ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME) + ${{ if ne(parameters.IgnoreDirectories, '') }}: + AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff --git a/eng/common/templates-official/steps/get-delegation-sas.yml b/eng/common/templates-official/steps/get-delegation-sas.yml new file mode 100644 index 000000000..c690cc0a0 --- /dev/null +++ b/eng/common/templates-official/steps/get-delegation-sas.yml @@ -0,0 +1,52 @@ +parameters: +- name: federatedServiceConnection + type: string +- name: outputVariableName + type: string +- name: expiryInHours + type: number + default: 1 +- name: base64Encode + type: boolean + default: false +- name: storageAccount + type: string +- name: container + type: string +- name: permissions + type: string + default: 'rl' + +steps: +- task: AzureCLI@2 + displayName: 'Generate delegation SAS Token for ${{ parameters.storageAccount }}/${{ parameters.container }}' + inputs: + azureSubscription: ${{ parameters.federatedServiceConnection }} + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # Calculate the expiration of the SAS token and convert to UTC + $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") + + # Temporarily work around a helix issue where SAS tokens with / in them will cause incorrect downloads + # of correlation payloads. https://github.com/dotnet/dnceng/issues/3484 + $sas = "" + do { + $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate SAS token." + exit 1 + } + } while($sas.IndexOf('/') -ne -1) + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate SAS token." + exit 1 + } + + if ('${{ parameters.base64Encode }}' -eq 'true') { + $sas = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sas)) + } + + Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" + Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true]$sas" diff --git a/eng/common/templates-official/steps/get-federated-access-token.yml b/eng/common/templates-official/steps/get-federated-access-token.yml new file mode 100644 index 000000000..55e33bd38 --- /dev/null +++ b/eng/common/templates-official/steps/get-federated-access-token.yml @@ -0,0 +1,40 @@ +parameters: +- name: federatedServiceConnection + type: string +- name: outputVariableName + type: string +- name: stepName + type: string + default: 'getFederatedAccessToken' +- name: condition + type: string + default: '' +# Resource to get a token for. Common values include: +# - '499b84ac-1321-427f-aa17-267ca6975798' for Azure DevOps +# - 'https://storage.azure.com/' for storage +# Defaults to Azure DevOps +- name: resource + type: string + default: '499b84ac-1321-427f-aa17-267ca6975798' +- name: isStepOutputVariable + type: boolean + default: false + +steps: +- task: AzureCLI@2 + displayName: 'Getting federated access token for feeds' + name: ${{ parameters.stepName }} + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + inputs: + azureSubscription: ${{ parameters.federatedServiceConnection }} + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + $accessToken = az account get-access-token --query accessToken --resource ${{ parameters.resource }} --output tsv + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to get access token for resource '${{ parameters.resource }}'" + exit 1 + } + Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" + Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true;isOutput=${{ parameters.isStepOutputVariable }}]$accessToken" \ No newline at end of file diff --git a/eng/common/templates-official/steps/publish-logs.yml b/eng/common/templates-official/steps/publish-logs.yml new file mode 100644 index 000000000..04012fed1 --- /dev/null +++ b/eng/common/templates-official/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates-official/steps/retain-build.yml b/eng/common/templates-official/steps/retain-build.yml new file mode 100644 index 000000000..83d97a26a --- /dev/null +++ b/eng/common/templates-official/steps/retain-build.yml @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff --git a/eng/common/templates-official/steps/send-to-helix.yml b/eng/common/templates-official/steps/send-to-helix.yml new file mode 100644 index 000000000..22f250130 --- /dev/null +++ b/eng/common/templates-official/steps/send-to-helix.yml @@ -0,0 +1,92 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + HelixProjectArguments: '' # optional -- arguments passed to the build command for helixpublish.proj + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates-official/steps/source-build.yml b/eng/common/templates-official/steps/source-build.yml new file mode 100644 index 000000000..829f17c34 --- /dev/null +++ b/eng/common/templates-official/steps/source-build.yml @@ -0,0 +1,129 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates-official/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + # If building on the internal project, the artifact feeds variable may be available (usually only if needed) + # In that case, call the feed setup script to add internal feeds corresponding to public ones. + # In addition, add an msbuild argument to copy the WIP from the repo to the target build location. + # This is because SetupNuGetSources.sh will alter the current NuGet.config file, and we need to preserve those + # changes. + internalRestoreArgs= + if [ '$(dn-bot-dnceng-artifact-feeds-rw)' != '$''(dn-bot-dnceng-artifact-feeds-rw)' ]; then + # Temporarily work around https://github.com/dotnet/arcade/issues/7709 + chmod +x $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh $(Build.SourcesDirectory)/NuGet.config $(dn-bot-dnceng-artifact-feeds-rw) + internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' + + # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. + # This only works if there is a username/email configured, which won't be the case in most CI runs. + git config --get user.email + if [ $? -ne 0 ]; then + git config user.email dn-bot@microsoft.com + git config user.name dn-bot + fi + fi + + # If building on the internal project, the internal storage variable may be available (usually only if needed) + # In that case, add variables to allow the download of internal runtimes if the specified versions are not found + # in the default public locations. + internalRuntimeDownloadArgs= + if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + fi + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + + baseOsArgs= + if [ '${{ parameters.platform.baseOS }}' != '' ]; then + baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + assetManifestFileName=SourceBuild_RidSpecific.xml + if [ '${{ parameters.platform.name }}' != '' ]; then + assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $internalRuntimeDownloadArgs \ + $internalRestoreArgs \ + $targetRidArgs \ + $runtimeOsArgs \ + $baseOsArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true \ + /p:AssetManifestFileName=$assetManifestFileName + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() + +# Manually inject component detection so that we can ignore the source build upstream cache, which contains +# a nupkg cache of input packages (a local feed). +# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' +# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets +- task: ComponentGovernanceComponentDetection@0 + displayName: Component Detection (Exclude upstream cache) + inputs: + ignoreDirectories: '$(Build.SourcesDirectory)/artifacts/source-build/self/src/artifacts/obj/source-built-upstream-cache' diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml new file mode 100644 index 000000000..1f308b24e --- /dev/null +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -0,0 +1,45 @@ +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. + +# Motivation: +# Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS +# (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing +# (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template +# file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. + +# How to use: +# This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). +# If we find alternate naming conventions in broad usage it can be added to the condition below. +# +# First, import the template in an arcade-ified repo to pick up the variables, e.g.: +# +# variables: +# - template: /eng/common/templates-official/variables/pool-providers.yml +# +# ... then anywhere specifying the pool provider use the runtime variables, +# $(DncEngInternalBuildPool) +# +# pool: +# name: $(DncEngInternalBuildPool) +# image: 1es-windows-2022 + +variables: + # Coalesce the target and source branches so we know when a PR targets a release branch + # If these variables are somehow missing, fall back to main (tends to have more capacity) + + # Any new -Svc alternative pools should have variables added here to allow for splitting work + + - name: DncEngInternalBuildPool + value: $[ + replace( + replace( + eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), + True, + 'NetCore1ESPool-Svc-Internal' + ), + False, + 'NetCore1ESPool-Internal' + ) + ] \ No newline at end of file diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml new file mode 100644 index 000000000..dbdd66d4a --- /dev/null +++ b/eng/common/templates-official/variables/sdl-variables.yml @@ -0,0 +1,7 @@ +variables: +# The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in +# sync with the packages.config file. +- name: DefaultGuardianVersion + value: 0.109.0 +- name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml new file mode 100644 index 000000000..7870f93bc --- /dev/null +++ b/eng/common/templates/job/execute-sdl.yml @@ -0,0 +1,139 @@ +parameters: + enable: 'false' # Whether the SDL validation job should execute or not + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + sdlContinueOnError: false # optional: determines whether to continue the build if the step errors; + # optional: determines if build artifacts should be downloaded. + downloadArtifacts: true + # optional: determines if this job should search the directory of downloaded artifacts for + # 'tar.gz' and 'zip' archive files and extract them before running SDL validation tasks. + extractArchiveArtifacts: false + dependsOn: '' # Optional: dependencies of the job + artifactNames: '' # Optional: patterns supplied to DownloadBuildArtifacts + # Usage: + # artifactNames: + # - 'BlobArtifacts' + # - 'Artifacts_Windows_NT_Release' + # Optional: download a list of pipeline artifacts. 'downloadArtifacts' controls build artifacts, + # not pipeline artifacts, so doesn't affect the use of this parameter. + pipelineArtifactNames: [] + +jobs: +- job: Run_SDL + dependsOn: ${{ parameters.dependsOn }} + displayName: Run SDL tool + condition: and(succeededOrFailed(), eq( ${{ parameters.enable }}, 'true')) + variables: + - group: DotNet-VSTS-Bot + - name: AzDOProjectName + value: ${{ parameters.AzDOProjectName }} + - name: AzDOPipelineId + value: ${{ parameters.AzDOPipelineId }} + - name: AzDOBuildId + value: ${{ parameters.AzDOBuildId }} + - template: /eng/common/templates/variables/sdl-variables.yml + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + - template: /eng/common/templates/variables/pool-providers.yml + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - checkout: self + clean: true + + # If the template caller didn't provide an AzDO parameter, set them all up as Maestro vars. + - ${{ if not(and(parameters.AzDOProjectName, parameters.AzDOPipelineId, parameters.AzDOBuildId)) }}: + - template: /eng/common/templates/post-build/setup-maestro-vars.yml + + - ${{ if ne(parameters.downloadArtifacts, 'false')}}: + - ${{ if ne(parameters.artifactNames, '') }}: + - ${{ each artifactName in parameters.artifactNames }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.artifactNames, '') }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: specific files + itemPattern: "**" + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + + - ${{ each artifactName in parameters.pipelineArtifactNames }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + + - powershell: eng/common/sdl/trim-assets-version.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts + displayName: Trim the version from the NuGet packages + continueOnError: ${{ parameters.sdlContinueOnError }} + + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + displayName: Extract Blob Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + displayName: Extract Package Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - ${{ if ne(parameters.extractArchiveArtifacts, 'false') }}: + - powershell: eng/common/sdl/extract-artifact-archives.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts + displayName: Extract Archive Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + + - template: /eng/common/templates/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: ${{ parameters.additionalParameters }} + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml new file mode 100644 index 000000000..e295031c0 --- /dev/null +++ b/eng/common/templates/job/job.yml @@ -0,0 +1,259 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + templateContext: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + enableBuildRetry: false + disableComponentGovernance: '' + componentGovernanceIgnoreDirectories: '' + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle template variable syntax + # example: + # - template: path/to/template.yml + # parameters: + # [key]: [value] + - ${{ if ne(variable.template, '') }}: + - template: ${{ variable.template }} + ${{ if ne(variable.parameters, '') }}: + parameters: ${{ variable.parameters }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: + - task: NuGetAuthenticate@1 + + - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} + continueOnError: true + + - template: /eng/common/templates/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.publish.logs, '')) }}: + - publish: artifacts/log + artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: Publish logs + continueOnError: true + condition: always() + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} + IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableBuildRetry, 'true') }}: + - publish: $(Build.SourcesDirectory)\eng\common\BuildConfiguration + artifact: BuildConfiguration + displayName: Publish build retry configuration + continueOnError: true diff --git a/eng/common/templates/job/onelocbuild.yml b/eng/common/templates/job/onelocbuild.yml new file mode 100644 index 000000000..60ab00c4d --- /dev/null +++ b/eng/common/templates/job/onelocbuild.yml @@ -0,0 +1,109 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: '' + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + ReusePr: true + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + GitHubOrg: dotnet + MirrorRepo: '' + MirrorBranch: main + condition: '' + JobNameSuffix: '' + +jobs: +- job: OneLocBuild${{ parameters.JobNameSuffix }} + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + isShouldReusePrSelected: ${{ parameters.ReusePr }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if ne(parameters.MirrorRepo, '') }}: + isMirrorRepoSelected: true + gitHubOrganization: ${{ parameters.GitHubOrg }} + mirrorRepo: ${{ parameters.MirrorRepo }} + mirrorBranch: ${{ parameters.MirrorBranch }} + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml new file mode 100644 index 000000000..cc2b346ba --- /dev/null +++ b/eng/common/templates/job/publish-build-assets.yml @@ -0,0 +1,156 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishAssetsImmediately: false + + artifactsPublishingAdditionalParameters: '' + + signingValidationAdditionalParameters: '' + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 + + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry + + variables: + - template: /eng/common/templates/variables/pool-providers.yml + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + - name: runCodesignValidationInjection + value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/post-build/common-variables.yml + + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: NetCore1ESPool-Publishing-Internal + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: NuGetAuthenticate@1 + + - task: AzureCLI@2 + displayName: Publish Build Assets + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/sdk-task.ps1 + arguments: > + -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:MaestroApiEndpoint=https://maestro.dot.net + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(BARBuildId) + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value "$(DefaultChannels)" + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(IsStableBuild) + + - task: PublishBuildArtifacts@1 + displayName: Publish ReleaseConfigs Artifact + inputs: + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - task: powershell@2 + displayName: Check if SymbolPublishingExclusionsFile.txt exists + inputs: + targetType: inline + script: | + $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + if(Test-Path -Path $symbolExclusionfile) + { + Write-Host "SymbolExclusionFile exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]true" + } + else{ + Write-Host "Symbols Exclusion file does not exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]false" + } + + - task: PublishBuildArtifacts@1 + displayName: Publish SymbolPublishingExclusionsFile Artifact + condition: eq(variables['SymbolExclusionFile'], 'true') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion 3 + -AzdoToken '$(System.AccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates/job/source-build.yml b/eng/common/templates/job/source-build.yml new file mode 100644 index 000000000..c48f95d93 --- /dev/null +++ b/eng/common/templates/job/source-build.yml @@ -0,0 +1,74 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + + # If set to true and running on a non-public project, + # Internal blob storage locations will be enabled. + # This is not enabled by default because many repositories do not need internal sources + # and do not need to have the required service connections approved in the pipeline. + enableInternalSources: false + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore-Svc-Public' ), False, 'NetCore-Public')] + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open + + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] + demands: ImageOverride -equals Build.Ubuntu.2204.Amd64 + + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - ${{ if eq(parameters.enableInternalSources, true) }}: + - template: /eng/common/templates/steps/enable-internal-runtimes.yml + - template: /eng/common/templates/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml new file mode 100644 index 000000000..8538f44ba --- /dev/null +++ b/eng/common/templates/job/source-index-stage1.yml @@ -0,0 +1,82 @@ +parameters: + runAsPublic: false + sourceIndexUploadPackageVersion: 2.0.0-20250425.2 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250425.2 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + condition: '' + dependsOn: '' + pool: '' + +jobs: +- job: SourceIndexStage1 + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + - name: SourceIndexUploadPackageVersion + value: ${{ parameters.sourceIndexUploadPackageVersion }} + - name: SourceIndexProcessBinlogPackageVersion + value: ${{ parameters.sourceIndexProcessBinlogPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - template: /eng/common/templates/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64.open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET 8 SDK + inputs: + packageType: sdk + version: 8.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + + - script: | + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(sourceIndexProcessBinlogPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(sourceIndexUploadPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + displayName: Download Tools + # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. + workingDirectory: $(Agent.TempDirectory) + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: AzureCLI@2 + displayName: Get stage 1 auth token + inputs: + azureSubscription: 'SourceDotNet Stage1 Publish' + addSpnToEnvironment: true + scriptType: 'ps' + scriptLocation: 'inlineScript' + inlineScript: | + echo "##vso[task.setvariable variable=ARM_CLIENT_ID;issecret=true]$env:servicePrincipalId" + echo "##vso[task.setvariable variable=ARM_ID_TOKEN;issecret=true]$env:idToken" + echo "##vso[task.setvariable variable=ARM_TENANT_ID;issecret=true]$env:tenantId" + + - script: | + az login --service-principal -u $(ARM_CLIENT_ID) --tenant $(ARM_TENANT_ID) --allow-no-subscriptions --federated-token $(ARM_ID_TOKEN) + displayName: "Login to Azure" + + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) -s netsourceindexstage1 -b stage1 + displayName: Upload stage1 artifacts to source index diff --git a/eng/common/templates/jobs/codeql-build.yml b/eng/common/templates/jobs/codeql-build.yml new file mode 100644 index 000000000..f7dc5ea4a --- /dev/null +++ b/eng/common/templates/jobs/codeql-build.yml @@ -0,0 +1,31 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + +jobs: +- template: /eng/common/templates/jobs/jobs.yml + parameters: + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml new file mode 100644 index 000000000..289bb2396 --- /dev/null +++ b/eng/common/templates/jobs/jobs.yml @@ -0,0 +1,97 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. + publishAssetsImmediately: false + + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff --git a/eng/common/templates/jobs/source-build.yml b/eng/common/templates/jobs/source-build.yml new file mode 100644 index 000000000..3ec997108 --- /dev/null +++ b/eng/common/templates/jobs/source-build.yml @@ -0,0 +1,54 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-9-amd64' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + + # If set to true and running on a non-public project, + # Internal nuget and blob storage locations will be enabled. + # This is not enabled by default because many repositories do not need internal sources + # and do not need to have the required service connections approved in the pipeline. + enableInternalSources: false + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + enableInternalSources: ${{ parameters.enableInternalSources }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} + enableInternalSources: ${{ parameters.enableInternalSources }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml new file mode 100644 index 000000000..173914f23 --- /dev/null +++ b/eng/common/templates/post-build/common-variables.yml @@ -0,0 +1,22 @@ +variables: + - group: Publish-Build-Assets + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro.dot.net" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml new file mode 100644 index 000000000..c3b6a3012 --- /dev/null +++ b/eng/common/templates/post-build/post-build.yml @@ -0,0 +1,283 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + +stages: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml + jobs: + - job: + displayName: NuGet Validation + condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + + - job: + displayName: SourceLink Validation + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + + - template: /eng/common/templates/job/execute-sdl.yml + parameters: + enable: ${{ parameters.SDLValidationParameters.enable }} + publishGuardianDirectoryToPipeline: ${{ parameters.SDLValidationParameters.publishGdn }} + additionalParameters: ${{ parameters.SDLValidationParameters.params }} + continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} + artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} + downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} + +- ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ else }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + - template: /eng/common/templates/variables/pool-providers.yml + jobs: + - job: + displayName: Publish Using Darc + timeoutInMinutes: 120 + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: VSEngSS-MicroBuild2022-1ES + demands: Cmd + # If it's not devdiv, it's dnceng + ${{ else }}: + name: NetCore1ESPool-Publishing-Internal + demands: ImageOverride -equals windows.vs2019.amd64 + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: NuGetAuthenticate@1 + + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -AzdoToken '$(System.AccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml new file mode 100644 index 000000000..64b9abc68 --- /dev/null +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -0,0 +1,70 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +steps: + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: AzureCLI@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + . $(Build.SourcesDirectory)\eng\common\tools.ps1 + $darc = Get-Darc + $buildInfo = & $darc get-build ` + --id ${{ parameters.BARBuildId }} ` + --extended ` + --output-format json ` + --ci ` + | convertFrom-Json + + $BarId = ${{ parameters.BARBuildId }} + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates/post-build/trigger-subscription.yml b/eng/common/templates/post-build/trigger-subscription.yml new file mode 100644 index 000000000..da669030d --- /dev/null +++ b/eng/common/templates/post-build/trigger-subscription.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/add-build-to-channel.yml b/eng/common/templates/steps/add-build-to-channel.yml new file mode 100644 index 000000000..f67a210d6 --- /dev/null +++ b/eng/common/templates/steps/add-build-to-channel.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/build-reason.yml b/eng/common/templates/steps/build-reason.yml new file mode 100644 index 000000000..eba58109b --- /dev/null +++ b/eng/common/templates/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml new file mode 100644 index 000000000..cbba05967 --- /dev/null +++ b/eng/common/templates/steps/component-governance.yml @@ -0,0 +1,13 @@ +parameters: + disableComponentGovernance: false + componentGovernanceIgnoreDirectories: '' + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true + inputs: + ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} \ No newline at end of file diff --git a/eng/common/templates/steps/enable-internal-runtimes.yml b/eng/common/templates/steps/enable-internal-runtimes.yml new file mode 100644 index 000000000..54dc9416c --- /dev/null +++ b/eng/common/templates/steps/enable-internal-runtimes.yml @@ -0,0 +1,28 @@ +# Obtains internal runtime download credentials and populates the 'dotnetbuilds-internal-container-read-token-base64' +# variable with the base64-encoded SAS token, by default + +parameters: +- name: federatedServiceConnection + type: string + default: 'dotnetbuilds-internal-read' +- name: outputVariableName + type: string + default: 'dotnetbuilds-internal-container-read-token-base64' +- name: expiryInHours + type: number + default: 1 +- name: base64Encode + type: boolean + default: true + +steps: +- ${{ if ne(variables['System.TeamProject'], 'public') }}: + - template: /eng/common/templates/steps/get-delegation-sas.yml + parameters: + federatedServiceConnection: ${{ parameters.federatedServiceConnection }} + outputVariableName: ${{ parameters.outputVariableName }} + expiryInHours: ${{ parameters.expiryInHours }} + base64Encode: ${{ parameters.base64Encode }} + storageAccount: dotnetbuilds + container: internal + permissions: rl diff --git a/eng/common/templates/steps/execute-codeql.yml b/eng/common/templates/steps/execute-codeql.yml new file mode 100644 index 000000000..3930b1630 --- /dev/null +++ b/eng/common/templates/steps/execute-codeql.yml @@ -0,0 +1,32 @@ +parameters: + # Language that should be analyzed. Defaults to csharp + language: csharp + # Build Commands + buildCommands: '' + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + # optional: determines whether to continue the build if the step errors; + sdlContinueOnError: false + +steps: +- template: /eng/common/templates/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: '${{ parameters.additionalParameters }} + -CodeQLAdditionalRunConfigParams @("BuildCommands < ${{ parameters.buildCommands }}", "Language < ${{ parameters.language }}")' + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} \ No newline at end of file diff --git a/eng/common/templates/steps/execute-sdl.yml b/eng/common/templates/steps/execute-sdl.yml new file mode 100644 index 000000000..fe0ebf8c9 --- /dev/null +++ b/eng/common/templates/steps/execute-sdl.yml @@ -0,0 +1,89 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- task: NuGetAuthenticate@1 + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL (Overridden) + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + env: + GUARDIAN_DEFAULT_PACKAGE_SOURCE_SECRET: $(System.AccessToken) + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianCliLocation $(GuardianCliLocation) + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + env: + GUARDIAN_DEFAULT_PACKAGE_SOURCE_SECRET: $(System.AccessToken) + +- ${{ if ne(parameters.publishGuardianDirectoryToPipeline, 'false') }}: + # We want to publish the Guardian results and configuration for easy diagnosis. However, the + # '.gdn' dir is a mix of configuration, results, extracted dependencies, and Guardian default + # tooling files. Some of these files are large and aren't useful during an investigation, so + # exclude them by simply deleting them before publishing. (As of writing, there is no documented + # way to selectively exclude a dir from the pipeline artifact publish task.) + - task: DeleteFiles@1 + displayName: Delete Guardian dependencies to avoid uploading + inputs: + SourceFolder: $(Agent.BuildDirectory)/.gdn + Contents: | + c + i + condition: succeededOrFailed() + + - publish: $(Agent.BuildDirectory)/.gdn + artifact: GuardianConfiguration + displayName: Publish GuardianConfiguration + condition: succeededOrFailed() + + # Publish the SARIF files in a container named CodeAnalysisLogs to enable integration + # with the "SARIF SAST Scans Tab" Azure DevOps extension + - task: CopyFiles@2 + displayName: Copy SARIF files + inputs: + flattenFolders: true + sourceFolder: $(Agent.BuildDirectory)/.gdn/rc/ + contents: '**/*.sarif' + targetFolder: $(Build.SourcesDirectory)/CodeAnalysisLogs + condition: succeededOrFailed() + + # Use PublishBuildArtifacts because the SARIF extension only checks this case + # see microsoft/sarif-azuredevops-extension#4 + - task: PublishBuildArtifacts@1 + displayName: Publish SARIF files to CodeAnalysisLogs container + inputs: + pathToPublish: $(Build.SourcesDirectory)/CodeAnalysisLogs + artifactName: CodeAnalysisLogs + condition: succeededOrFailed() \ No newline at end of file diff --git a/eng/common/templates/steps/generate-sbom.yml b/eng/common/templates/steps/generate-sbom.yml new file mode 100644 index 000000000..2b21eae42 --- /dev/null +++ b/eng/common/templates/steps/generate-sbom.yml @@ -0,0 +1,48 @@ +# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. +# PackageName - The name of the package this SBOM represents. +# PackageVersion - The version of the package this SBOM represents. +# ManifestDirPath - The path of the directory where the generated manifest files will be placed +# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. + +parameters: + PackageVersion: 8.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + IgnoreDirectories: '' + sbomContinueOnError: true + +steps: +- task: PowerShell@2 + displayName: Prep for SBOM generation in (Non-linux) + condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) + inputs: + filePath: ./eng/common/generate-sbom-prep.ps1 + arguments: ${{parameters.manifestDirPath}} + +# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 +- script: | + chmod +x ./eng/common/generate-sbom-prep.sh + ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} + displayName: Prep for SBOM generation in (Linux) + condition: eq(variables['Agent.Os'], 'Linux') + continueOnError: ${{ parameters.sbomContinueOnError }} + +- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM manifest' + continueOnError: ${{ parameters.sbomContinueOnError }} + inputs: + PackageName: ${{ parameters.packageName }} + BuildDropPath: ${{ parameters.buildDropPath }} + PackageVersion: ${{ parameters.packageVersion }} + ManifestDirPath: ${{ parameters.manifestDirPath }} + ${{ if ne(parameters.IgnoreDirectories, '') }}: + AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' + +- task: PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff --git a/eng/common/templates/steps/get-delegation-sas.yml b/eng/common/templates/steps/get-delegation-sas.yml new file mode 100644 index 000000000..c690cc0a0 --- /dev/null +++ b/eng/common/templates/steps/get-delegation-sas.yml @@ -0,0 +1,52 @@ +parameters: +- name: federatedServiceConnection + type: string +- name: outputVariableName + type: string +- name: expiryInHours + type: number + default: 1 +- name: base64Encode + type: boolean + default: false +- name: storageAccount + type: string +- name: container + type: string +- name: permissions + type: string + default: 'rl' + +steps: +- task: AzureCLI@2 + displayName: 'Generate delegation SAS Token for ${{ parameters.storageAccount }}/${{ parameters.container }}' + inputs: + azureSubscription: ${{ parameters.federatedServiceConnection }} + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # Calculate the expiration of the SAS token and convert to UTC + $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") + + # Temporarily work around a helix issue where SAS tokens with / in them will cause incorrect downloads + # of correlation payloads. https://github.com/dotnet/dnceng/issues/3484 + $sas = "" + do { + $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate SAS token." + exit 1 + } + } while($sas.IndexOf('/') -ne -1) + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate SAS token." + exit 1 + } + + if ('${{ parameters.base64Encode }}' -eq 'true') { + $sas = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sas)) + } + + Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" + Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true]$sas" diff --git a/eng/common/templates/steps/get-federated-access-token.yml b/eng/common/templates/steps/get-federated-access-token.yml new file mode 100644 index 000000000..55e33bd38 --- /dev/null +++ b/eng/common/templates/steps/get-federated-access-token.yml @@ -0,0 +1,40 @@ +parameters: +- name: federatedServiceConnection + type: string +- name: outputVariableName + type: string +- name: stepName + type: string + default: 'getFederatedAccessToken' +- name: condition + type: string + default: '' +# Resource to get a token for. Common values include: +# - '499b84ac-1321-427f-aa17-267ca6975798' for Azure DevOps +# - 'https://storage.azure.com/' for storage +# Defaults to Azure DevOps +- name: resource + type: string + default: '499b84ac-1321-427f-aa17-267ca6975798' +- name: isStepOutputVariable + type: boolean + default: false + +steps: +- task: AzureCLI@2 + displayName: 'Getting federated access token for feeds' + name: ${{ parameters.stepName }} + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + inputs: + azureSubscription: ${{ parameters.federatedServiceConnection }} + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + $accessToken = az account get-access-token --query accessToken --resource ${{ parameters.resource }} --output tsv + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to get access token for resource '${{ parameters.resource }}'" + exit 1 + } + Write-Host "Setting '${{ parameters.outputVariableName }}' with the access token value" + Write-Host "##vso[task.setvariable variable=${{ parameters.outputVariableName }};issecret=true;isOutput=${{ parameters.isStepOutputVariable }}]$accessToken" \ No newline at end of file diff --git a/eng/common/templates/steps/publish-logs.yml b/eng/common/templates/steps/publish-logs.yml new file mode 100644 index 000000000..88f238f36 --- /dev/null +++ b/eng/common/templates/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates/steps/retain-build.yml b/eng/common/templates/steps/retain-build.yml new file mode 100644 index 000000000..83d97a26a --- /dev/null +++ b/eng/common/templates/steps/retain-build.yml @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff --git a/eng/common/templates/steps/run-on-unix.yml b/eng/common/templates/steps/run-on-unix.yml new file mode 100644 index 000000000..e1733814f --- /dev/null +++ b/eng/common/templates/steps/run-on-unix.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if ne(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-on-windows.yml b/eng/common/templates/steps/run-on-windows.yml new file mode 100644 index 000000000..73e7e9c27 --- /dev/null +++ b/eng/common/templates/steps/run-on-windows.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if eq(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-script-ifequalelse.yml b/eng/common/templates/steps/run-script-ifequalelse.yml new file mode 100644 index 000000000..3d1242f55 --- /dev/null +++ b/eng/common/templates/steps/run-script-ifequalelse.yml @@ -0,0 +1,33 @@ +parameters: + # if parameter1 equals parameter 2, run 'ifScript' command, else run 'elsescript' command + parameter1: '' + parameter2: '' + ifScript: '' + elseScript: '' + + # name of script step + name: Script + + # display name of script step + displayName: If-Equal-Else Script + + # environment + env: {} + + # conditional expression for step execution + condition: '' + +steps: +- ${{ if and(ne(parameters.ifScript, ''), eq(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.ifScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} + +- ${{ if and(ne(parameters.elseScript, ''), ne(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.elseScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml new file mode 100644 index 000000000..22f250130 --- /dev/null +++ b/eng/common/templates/steps/send-to-helix.yml @@ -0,0 +1,92 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + HelixProjectArguments: '' # optional -- arguments passed to the build command for helixpublish.proj + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml new file mode 100644 index 000000000..41bbb9157 --- /dev/null +++ b/eng/common/templates/steps/source-build.yml @@ -0,0 +1,129 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + # If building on the internal project, the artifact feeds variable may be available (usually only if needed) + # In that case, call the feed setup script to add internal feeds corresponding to public ones. + # In addition, add an msbuild argument to copy the WIP from the repo to the target build location. + # This is because SetupNuGetSources.sh will alter the current NuGet.config file, and we need to preserve those + # changes. + internalRestoreArgs= + if [ '$(dn-bot-dnceng-artifact-feeds-rw)' != '$''(dn-bot-dnceng-artifact-feeds-rw)' ]; then + # Temporarily work around https://github.com/dotnet/arcade/issues/7709 + chmod +x $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh $(Build.SourcesDirectory)/NuGet.config $(dn-bot-dnceng-artifact-feeds-rw) + internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' + + # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. + # This only works if there is a username/email configured, which won't be the case in most CI runs. + git config --get user.email + if [ $? -ne 0 ]; then + git config user.email dn-bot@microsoft.com + git config user.name dn-bot + fi + fi + + # If building on the internal project, the internal storage variable may be available (usually only if needed) + # In that case, add variables to allow the download of internal runtimes if the specified versions are not found + # in the default public locations. + internalRuntimeDownloadArgs= + if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + fi + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + + baseOsArgs= + if [ '${{ parameters.platform.baseOS }}' != '' ]; then + baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + assetManifestFileName=SourceBuild_RidSpecific.xml + if [ '${{ parameters.platform.name }}' != '' ]; then + assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $internalRuntimeDownloadArgs \ + $internalRestoreArgs \ + $targetRidArgs \ + $runtimeOsArgs \ + $baseOsArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true \ + /p:AssetManifestFileName=$assetManifestFileName + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() + +# Manually inject component detection so that we can ignore the source build upstream cache, which contains +# a nupkg cache of input packages (a local feed). +# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' +# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets +- task: ComponentGovernanceComponentDetection@0 + displayName: Component Detection (Exclude upstream cache) + inputs: + ignoreDirectories: '$(Build.SourcesDirectory)/artifacts/source-build/self/src/artifacts/obj/source-built-upstream-cache' diff --git a/eng/common/templates/steps/telemetry-end.yml b/eng/common/templates/steps/telemetry-end.yml new file mode 100644 index 000000000..fadc04ca1 --- /dev/null +++ b/eng/common/templates/steps/telemetry-end.yml @@ -0,0 +1,102 @@ +parameters: + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- bash: | + if [ "$AGENT_JOBSTATUS" = "Succeeded" ] || [ "$AGENT_JOBSTATUS" = "PartiallySucceeded" ]; then + errorCount=0 + else + errorCount=1 + fi + warningCount=0 + + curlStatus=1 + retryCount=0 + # retry loop to harden against spotty telemetry connections + # we don't retry successes and 4xx client errors + until [[ $curlStatus -eq 0 || ( $curlStatus -ge 400 && $curlStatus -le 499 ) || $retryCount -ge $MaxRetries ]] + do + if [ $retryCount -gt 0 ]; then + echo "Failed to send telemetry to Helix; waiting $RetryDelay seconds before retrying..." + sleep $RetryDelay + fi + + # create a temporary file for curl output + res=`mktemp` + + curlResult=` + curl --verbose --output $res --write-out "%{http_code}"\ + -H 'Content-Type: application/json' \ + -H "X-Helix-Job-Token: $Helix_JobToken" \ + -H 'Content-Length: 0' \ + -X POST -G "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$Helix_WorkItemId/finish" \ + --data-urlencode "errorCount=$errorCount" \ + --data-urlencode "warningCount=$warningCount"` + curlStatus=$? + + if [ $curlStatus -eq 0 ]; then + if [ $curlResult -gt 299 ] || [ $curlResult -lt 200 ]; then + curlStatus=$curlResult + fi + fi + + let retryCount++ + done + + if [ $curlStatus -ne 0 ]; then + echo "Failed to Send Build Finish information after $retryCount retries" + vstsLogOutput="vso[task.logissue type=error;sourcepath=templates/steps/telemetry-end.yml;code=1;]Failed to Send Build Finish information: $curlStatus" + echo "##$vstsLogOutput" + exit 1 + fi + displayName: Send Unix Build End Telemetry + env: + # defined via VSTS variables in start-job.sh + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(), ne(variables['Agent.Os'], 'Windows_NT')) +- powershell: | + if (($env:Agent_JobStatus -eq 'Succeeded') -or ($env:Agent_JobStatus -eq 'PartiallySucceeded')) { + $ErrorCount = 0 + } else { + $ErrorCount = 1 + } + $WarningCount = 0 + + # Basic retry loop to harden against server flakiness + $retryCount = 0 + while ($retryCount -lt $env:MaxRetries) { + try { + Invoke-RestMethod -Uri "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$env:Helix_WorkItemId/finish?errorCount=$ErrorCount&warningCount=$WarningCount" -Method Post -ContentType "application/json" -Body "" ` + -Headers @{ 'X-Helix-Job-Token'=$env:Helix_JobToken } + break + } + catch { + $statusCode = $_.Exception.Response.StatusCode.value__ + if ($statusCode -ge 400 -and $statusCode -le 499) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix (status code $statusCode); not retrying (4xx client error)" + Write-Host "##vso[task.logissue]error ", $_.Exception.GetType().FullName, $_.Exception.Message + exit 1 + } + Write-Host "Failed to send telemetry to Helix (status code $statusCode); waiting $env:RetryDelay seconds before retrying..." + $retryCount++ + sleep $env:RetryDelay + continue + } + } + + if ($retryCount -ge $env:MaxRetries) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix after $retryCount retries." + exit 1 + } + displayName: Send Windows Build End Telemetry + env: + # defined via VSTS variables in start-job.ps1 + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(),eq(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/templates/steps/telemetry-start.yml b/eng/common/templates/steps/telemetry-start.yml new file mode 100644 index 000000000..6abbcb33a --- /dev/null +++ b/eng/common/templates/steps/telemetry-start.yml @@ -0,0 +1,241 @@ +parameters: + helixSource: 'undefined_defaulted_in_telemetry.yml' + helixType: 'undefined_defaulted_in_telemetry.yml' + buildConfig: '' + runAsPublic: false + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- ${{ if and(eq(parameters.runAsPublic, 'false'), not(eq(variables['System.TeamProject'], 'public'))) }}: + - task: AzureKeyVault@2 + inputs: + azureSubscription: 'HelixProd_KeyVault' + KeyVaultName: HelixProdKV + SecretsFilter: 'HelixApiAccessToken' + condition: always() +- bash: | + # create a temporary file + jobInfo=`mktemp` + + # write job info content to temporary file + cat > $jobInfo < powershell invocations +# as dot sourcing isn't possible. +function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { + if (Test-Path variable:global:_DotNetInstallDir) { + return $global:_DotNetInstallDir + } + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + $env:DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we do not need all ASP.NET packages restored. + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI. + if ($ci) { + $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 + } + + # Source Build uses DotNetCoreSdkDir variable + if ($env:DotNetCoreSdkDir -ne $null) { + $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir + } + + # Find the first path on %PATH% that contains the dotnet.exe + if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { + $dotnetExecutable = GetExecutableFileName 'dotnet' + $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue + + if ($dotnetCmd -ne $null) { + $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent + } + } + + $dotnetSdkVersion = $GlobalJson.tools.dotnet + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { + $dotnetRoot = $env:DOTNET_INSTALL_DIR + } else { + $dotnetRoot = Join-Path $RepoRoot '.dotnet' + + if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { + if ($install) { + InstallDotNetSdk $dotnetRoot $dotnetSdkVersion + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + ExitWithExitCode 1 + } + } + + $env:DOTNET_INSTALL_DIR = $dotnetRoot + } + + # Creates a temporary file under the toolset dir. + # The following code block is protecting against concurrent access so that this function can + # be called in parallel. + if ($createSdkLocationFile) { + do { + $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) + } + until (!(Test-Path $sdkCacheFileTemp)) + Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot + + try { + Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') + } catch { + # Somebody beat us + Remove-Item -Path $sdkCacheFileTemp + } + } + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + # It also ensures that VS msbuild will use the downloaded sdk targets. + $env:PATH = "$dotnetRoot;$env:PATH" + + # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build + Write-PipelinePrependPath -Path $dotnetRoot + + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' + Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' + + return $global:_DotNetInstallDir = $dotnetRoot +} + +function Retry($downloadBlock, $maxRetries = 5) { + $retries = 1 + + while($true) { + try { + & $downloadBlock + break + } + catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + } + + if (++$retries -le $maxRetries) { + $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff + Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." + Start-Sleep -Seconds $delayInSeconds + } + else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." + break + } + + } +} + +function GetDotNetInstallScript([string] $dotnetRoot) { + $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' + if (!(Test-Path $installScript)) { + Create-Directory $dotnetRoot + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + + Retry({ + Write-Host "GET $uri" + Invoke-WebRequest $uri -OutFile $installScript + }) + } + + return $installScript +} + +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { + InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath +} + +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = '', + [string] $runtime = '', + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $noPath) { + + $dotnetVersionLabel = "'sdk v$version'" + + if ($runtime -ne '' -and $runtime -ne 'sdk') { + $runtimePath = $dotnetRoot + $runtimePath = $runtimePath + "\shared" + if ($runtime -eq "dotnet") { $runtimePath = $runtimePath + "\Microsoft.NETCore.App" } + if ($runtime -eq "aspnetcore") { $runtimePath = $runtimePath + "\Microsoft.AspNetCore.App" } + if ($runtime -eq "windowsdesktop") { $runtimePath = $runtimePath + "\Microsoft.WindowsDesktop.App" } + $runtimePath = $runtimePath + "\" + $version + + $dotnetVersionLabel = "runtime toolset '$runtime/$architecture v$version'" + + if (Test-Path $runtimePath) { + Write-Host " Runtime toolset '$runtime/$architecture v$version' already installed." + $installSuccess = $true + Exit + } + } + + $installScript = GetDotNetInstallScript $dotnetRoot + $installParameters = @{ + Version = $version + InstallDir = $dotnetRoot + } + + if ($architecture) { $installParameters.Architecture = $architecture } + if ($runtime) { $installParameters.Runtime = $runtime } + if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } + if ($noPath) { $installParameters.NoPath = $True } + + $variations = @() + $variations += @($installParameters) + + $dotnetBuilds = $installParameters.Clone() + $dotnetbuilds.AzureFeed = "https://ci.dot.net/public" + $variations += @($dotnetBuilds) + + if ($runtimeSourceFeed) { + $runtimeSource = $installParameters.Clone() + $runtimeSource.AzureFeed = $runtimeSourceFeed + if ($runtimeSourceFeedKey) { + $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) + $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) + $runtimeSource.FeedCredential = $decodedString + } + $variations += @($runtimeSource) + } + + $installSuccess = $false + foreach ($variation in $variations) { + if ($variation | Get-Member AzureFeed) { + $location = $variation.AzureFeed + } else { + $location = "public location"; + } + Write-Host " Attempting to install $dotnetVersionLabel from $location." + try { + & $installScript @variation + $installSuccess = $true + break + } + catch { + Write-Host " Failed to install $dotnetVersionLabel from $location." + } + } + if (-not $installSuccess) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install $dotnetVersionLabel from any of the specified locations." + ExitWithExitCode 1 + } +} + +# +# Locates Visual Studio MSBuild installation. +# The preference order for MSBuild to use is as follows: +# +# 1. MSBuild from an active VS command prompt +# 2. MSBuild from a compatible VS installation +# 3. MSBuild from the xcopy tool package +# +# Returns full path to msbuild.exe. +# Throws on failure. +# +function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { + if (-not (IsWindowsPlatform)) { + throw "Cannot initialize Visual Studio on non-Windows" + } + + if (Test-Path variable:global:_MSBuildExe) { + return $global:_MSBuildExe + } + + # Minimum VS version to require. + $vsMinVersionReqdStr = '17.7' + $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) + + # If the version of msbuild is going to be xcopied, + # use this version. Version matches a package here: + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.12.0 + $defaultXCopyMSBuildVersion = '17.12.0' + + if (!$vsRequirements) { + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + $vsRequirements = $GlobalJson.tools.vs + } + else { + $vsRequirements = New-Object PSObject -Property @{ version = $vsMinVersionReqdStr } + } + } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } + $vsMinVersion = [Version]::new($vsMinVersionStr) + + # Try msbuild command available in the environment. + if ($env:VSINSTALLDIR -ne $null) { + $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue + if ($msbuildCmd -ne $null) { + # Workaround for https://github.com/dotnet/roslyn/issues/35793 + # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) + + if ($msbuildVersion -ge $vsMinVersion) { + return $global:_MSBuildExe = $msbuildCmd.Path + } + + # Report error - the developer environment is initialized with incompatible VS version. + throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" + } + } + + # Locate Visual Studio installation or download x-copy msbuild. + $vsInfo = LocateVisualStudio $vsRequirements + if ($vsInfo -ne $null) { + # Ensure vsInstallDir has a trailing slash + $vsInstallDir = Join-Path $vsInfo.installationPath "\" + $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] + + InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion + } else { + + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { + $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } else { + #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download + if($vsMinVersion -lt $vsMinVersionReqd){ + Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" + $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } + else{ + # If the VS version IS compatible, look for an xcopy msbuild package + # with a version matching VS. + # Note: If this version does not exist, then an explicit version of xcopy msbuild + # can be specified in global.json. This will be required for pre-release versions of msbuild. + $vsMajorVersion = $vsMinVersion.Major + $vsMinorVersion = $vsMinVersion.Minor + $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" + } + } + + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + if ($vsInstallDir -eq $null) { + throw "Could not xcopy msbuild. Please check that package 'RoslynTools.MSBuild @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." + } + } + if ($vsInstallDir -eq $null) { + throw 'Unable to find Visual Studio that has required version and components installed' + } + } + + $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } + + $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" + $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false } + if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) { + $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe" + } else { + $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" + } + + return $global:_MSBuildExe +} + +function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { + $env:VSINSTALLDIR = $vsInstallDir + Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") + + $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" + if (Test-Path $vsSdkInstallDir) { + Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir + $env:VSSDKInstall = $vsSdkInstallDir + } +} + +function InstallXCopyMSBuild([string]$packageVersion) { + return InitializeXCopyMSBuild $packageVersion -install $true +} + +function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { + $packageName = 'RoslynTools.MSBuild' + $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" + $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" + + if (!(Test-Path $packageDir)) { + if (!$install) { + return $null + } + + Create-Directory $packageDir + + Write-Host "Downloading $packageName $packageVersion" + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + Retry({ + Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -OutFile $packagePath + }) + + Unzip $packagePath $packageDir + } + + return Join-Path $packageDir 'tools' +} + +# +# Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. +# +# The following properties of tools.vs are recognized: +# "version": "{major}.{minor}" +# Two part minimal VS version, e.g. "15.9", "16.0", etc. +# "components": ["componentId1", "componentId2", ...] +# Array of ids of workload components that must be available in the VS instance. +# See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 +# +# Returns JSON describing the located VS instance (same format as returned by vswhere), +# or $null if no instance meeting the requirements is found on the machine. +# +function LocateVisualStudio([object]$vsRequirements = $null){ + if (-not (IsWindowsPlatform)) { + throw "Cannot run vswhere on non-Windows platforms." + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { + $vswhereVersion = $GlobalJson.tools.vswhere + } else { + $vswhereVersion = '2.5.2' + } + + $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" + $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' + + if (!(Test-Path $vsWhereExe)) { + Create-Directory $vsWhereDir + Write-Host 'Downloading vswhere' + Retry({ + Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + }) + } + + if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } + $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') + + if (!$excludePrereleaseVS) { + $args += '-prerelease' + } + + if (Get-Member -InputObject $vsRequirements -Name 'version') { + $args += '-version' + $args += $vsRequirements.version + } + + if (Get-Member -InputObject $vsRequirements -Name 'components') { + foreach ($component in $vsRequirements.components) { + $args += '-requires' + $args += $component + } + } + + $vsInfo =& $vsWhereExe $args | ConvertFrom-Json + + if ($lastExitCode -ne 0) { + return $null + } + + # use first matching instance + return $vsInfo[0] +} + +function InitializeBuildTool() { + if (Test-Path variable:global:_BuildTool) { + # If the requested msbuild parameters do not match, clear the cached variables. + if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { + Remove-Item variable:global:_BuildTool + Remove-Item variable:global:_MSBuildExe + } else { + return $global:_BuildTool + } + } + + if (-not $msbuildEngine) { + $msbuildEngine = GetDefaultMSBuildEngine + } + + # Initialize dotnet cli if listed in 'tools' + $dotnetRoot = $null + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + $dotnetRoot = InitializeDotNetCli -install:$restore + } + + if ($msbuildEngine -eq 'dotnet') { + if (!$dotnetRoot) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." + ExitWithExitCode 1 + } + $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') + + # Use override if it exists - commonly set by source-build + if ($null -eq $env:_OverrideArcadeInitializeBuildToolFramework) { + $initializeBuildToolFramework="net8.0" + } else { + $initializeBuildToolFramework=$env:_OverrideArcadeInitializeBuildToolFramework + } + + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = $initializeBuildToolFramework } + } elseif ($msbuildEngine -eq "vs") { + try { + $msbuildPath = InitializeVisualStudioMSBuild -install:$restore + } catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 + } + + $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472"; ExcludePrereleaseVS = $excludePrereleaseVS } + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + ExitWithExitCode 1 + } + + return $global:_BuildTool = $buildTool +} + +function GetDefaultMSBuildEngine() { + # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + return 'vs' + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + return 'dotnet' + } + + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + ExitWithExitCode 1 +} + +function GetNuGetPackageCachePath() { + if ($env:NUGET_PACKAGES -eq $null) { + # Use local cache on CI to ensure deterministic build. + # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 + # use global cache in dev builds to avoid cost of downloading packages. + # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 + if ($useGlobalNuGetCache) { + $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' + } else { + $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' + $env:RESTORENOCACHE = $true + } + } + + return $env:NUGET_PACKAGES +} + +# Returns a full path to an Arcade SDK task project file. +function GetSdkTaskProject([string]$taskName) { + return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj" +} + +function InitializeNativeTools() { + if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { + $nativeArgs= @{} + if ($ci) { + $nativeArgs = @{ + InstallDirectory = "$ToolsDir" + } + } + if ($env:NativeToolsOnMachine) { + Write-Host "Variable NativeToolsOnMachine detected, enabling native tool path promotion..." + $nativeArgs += @{ PathPromotion = $true } + } + & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs + } +} + +function Read-ArcadeSdkVersion() { + return $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' +} + +function InitializeToolset() { + if (Test-Path variable:global:_ToolsetBuildProj) { + return $global:_ToolsetBuildProj + } + + $nugetCache = GetNuGetPackageCachePath + + $toolsetVersion = Read-ArcadeSdkVersion + $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt" + + if (Test-Path $toolsetLocationFile) { + $path = Get-Content $toolsetLocationFile -TotalCount 1 + if (Test-Path $path) { + return $global:_ToolsetBuildProj = $path + } + } + + if (-not $restore) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." + ExitWithExitCode 1 + } + + $buildTool = InitializeBuildTool + + $proj = Join-Path $ToolsetDir 'restore.proj' + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } + + '' | Set-Content $proj + + MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile + + $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 + if (!(Test-Path $path)) { + throw "Invalid toolset path: $path" + } + + return $global:_ToolsetBuildProj = $path +} + +function ExitWithExitCode([int] $exitCode) { + if ($ci -and $prepareMachine) { + Stop-Processes + } + exit $exitCode +} + +# Check if $LASTEXITCODE is a nonzero exit code (NZEC). If so, print a Azure Pipeline error for +# diagnostics, then exit the script with the $LASTEXITCODE. +function Exit-IfNZEC([string] $category = "General") { + Write-Host "Exit code $LASTEXITCODE" + if ($LASTEXITCODE -ne 0) { + $message = "Last command failed with exit code $LASTEXITCODE." + Write-PipelineTelemetryError -Force -Category $category -Message $message + ExitWithExitCode $LASTEXITCODE + } +} + +function Stop-Processes() { + Write-Host 'Killing running build processes...' + foreach ($processName in $processesToStopOnExit) { + Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process + } +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild() { + if ($pipelinesLog) { + $buildTool = InitializeBuildTool + + if ($ci -and $buildTool.Tool -eq 'dotnet') { + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' + } + + Enable-Nuget-EnhancedRetry + + $toolsetBuildProject = InitializeToolset + $basePath = Split-Path -parent $toolsetBuildProject + $possiblePaths = @( + # new scripts need to work with old packages, so we need to look for the old names/versions + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp3.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path net7.0 'Microsoft.DotNet.Arcade.Sdk.dll')) + ) + $selectedPath = $null + foreach ($path in $possiblePaths) { + if (Test-Path $path -PathType Leaf) { + $selectedPath = $path + break + } + } + if (-not $selectedPath) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' + ExitWithExitCode 1 + } + $args += "/logger:$selectedPath" + } + + MSBuild-Core @args +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild-Core() { + if ($ci) { + if (!$binaryLog -and !$excludeCIBinarylog) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' + ExitWithExitCode 1 + } + + if ($nodeReuse) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' + ExitWithExitCode 1 + } + } + + Enable-Nuget-EnhancedRetry + + $buildTool = InitializeBuildTool + + $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" + + if ($warnAsError) { + $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' + } + else { + $cmdArgs += ' /p:TreatWarningsAsErrors=false' + } + + foreach ($arg in $args) { + if ($null -ne $arg -and $arg.Trim() -ne "") { + if ($arg.EndsWith('\')) { + $arg = $arg + "\" + } + $cmdArgs += " `"$arg`"" + } + } + + $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs" + + $exitCode = Exec-Process $buildTool.Path $cmdArgs + + if ($exitCode -ne 0) { + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red + + $buildLog = GetMSBuildBinaryLogCommandLineArgument $args + if ($null -ne $buildLog) { + Write-Host "See log: $buildLog" -ForegroundColor DarkGray + } + + # When running on Azure Pipelines, override the returned exit code to avoid double logging. + if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null) { + Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + } else { + ExitWithExitCode $exitCode + } + } +} + +function GetMSBuildBinaryLogCommandLineArgument($arguments) { + foreach ($argument in $arguments) { + if ($argument -ne $null) { + $arg = $argument.Trim() + if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { + return $arg.Substring('/bl:'.Length) + } + + if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { + return $arg.Substring('/binaryLogger:'.Length) + } + } + } + + return $null +} + +function GetExecutableFileName($baseName) { + if (IsWindowsPlatform) { + return "$baseName.exe" + } + else { + return $baseName + } +} + +function IsWindowsPlatform() { + return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT +} + +function Get-Darc($version) { + $darcPath = "$TempDir\darc\$([guid]::NewGuid())" + if ($version -ne $null) { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host + } else { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host + } + return "$darcPath\darc.exe" +} + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\') +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$ToolsetDir = Join-Path $ArtifactsDir 'toolset' +$ToolsDir = Join-Path $RepoRoot '.tools' +$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json +# true if global.json contains a "runtimes" section +$globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } + +Create-Directory $ToolsetDir +Create-Directory $TempDir +Create-Directory $LogDir + +Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir +Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir +Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir +Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir +Write-PipelineSetVariable -Name 'TMP' -Value $TempDir + +# Import custom tools configuration, if present in the repo. +# Note: Import in global scope so that the script set top-level variables without qualification. +if (!$disableConfigureToolsetImport) { + $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { + if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' + ExitWithExitCode $LastExitCode + } + } + } +} + +# +# If $ci flag is set, turn on (and log that we did) special environment variables for improved Nuget client retry logic. +# +function Enable-Nuget-EnhancedRetry() { + if ($ci) { + Write-Host "Setting NUGET enhanced retry environment variables" + $env:NUGET_ENABLE_ENHANCED_HTTP_RETRY = 'true' + $env:NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT = 6 + $env:NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS = 1000 + $env:NUGET_RETRY_HTTP_429 = 'true' + Write-PipelineSetVariable -Name 'NUGET_ENABLE_ENHANCED_HTTP_RETRY' -Value 'true' + Write-PipelineSetVariable -Name 'NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT' -Value '6' + Write-PipelineSetVariable -Name 'NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS' -Value '1000' + Write-PipelineSetVariable -Name 'NUGET_RETRY_HTTP_429' -Value 'true' + } +} diff --git a/eng/common/tools.sh b/eng/common/tools.sh new file mode 100644 index 000000000..68db15430 --- /dev/null +++ b/eng/common/tools.sh @@ -0,0 +1,592 @@ +#!/usr/bin/env bash + +# Initialize variables if they aren't already defined. + +# CI mode - set to true on CI server for PR validation build or official build. +ci=${ci:-false} + +# Set to true to use the pipelines logger which will enable Azure logging output. +# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md +# This flag is meant as a temporary opt-opt for the feature while validate it across +# our consumers. It will be deleted in the future. +if [[ "$ci" == true ]]; then + pipelines_log=${pipelines_log:-true} +else + pipelines_log=${pipelines_log:-false} +fi + +# Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. +configuration=${configuration:-'Debug'} + +# Set to true to opt out of outputting binary log while running in CI +exclude_ci_binary_log=${exclude_ci_binary_log:-false} + +if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then + binary_log_default=true +else + binary_log_default=false +fi + +# Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. +binary_log=${binary_log:-$binary_log_default} + +# Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). +prepare_machine=${prepare_machine:-false} + +# True to restore toolsets and dependencies. +restore=${restore:-true} + +# Adjusts msbuild verbosity level. +verbosity=${verbosity:-'minimal'} + +# Set to true to reuse msbuild nodes. Recommended to not reuse on CI. +if [[ "$ci" == true ]]; then + node_reuse=${node_reuse:-false} +else + node_reuse=${node_reuse:-true} +fi + +# Configures warning treatment in msbuild. +warn_as_error=${warn_as_error:-true} + +# True to attempt using .NET Core already that meets requirements specified in global.json +# installed on the machine instead of downloading one. +use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} + +# Enable repos to use a particular version of the on-line dotnet-install scripts. +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh +dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} + +# True to use global NuGet cache instead of restoring packages to repository-local directory. +if [[ "$ci" == true ]]; then + use_global_nuget_cache=${use_global_nuget_cache:-false} +else + use_global_nuget_cache=${use_global_nuget_cache:-true} +fi + +# Used when restoring .NET SDK from alternative feeds +runtime_source_feed=${runtime_source_feed:-''} +runtime_source_feed_key=${runtime_source_feed_key:-''} + +# Resolve any symlinks in the given path. +function ResolvePath { + local path=$1 + + while [[ -h $path ]]; do + local dir="$( cd -P "$( dirname "$path" )" && pwd )" + path="$(readlink "$path")" + + # if $path was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $path != /* ]] && path="$dir/$path" + done + + # return value + _ResolvePath="$path" +} + +# ReadVersionFromJson [json key] +function ReadGlobalVersion { + local key=$1 + + if command -v jq &> /dev/null; then + _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" + elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then + _ReadGlobalVersion=${BASH_REMATCH[1]} + fi + + if [[ -z "$_ReadGlobalVersion" ]]; then + Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" + ExitWithExitCode 1 + fi +} + +function InitializeDotNetCli { + if [[ -n "${_InitializeDotNetCli:-}" ]]; then + return + fi + + local install=$1 + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + export DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we want to control all package sources + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI + if [[ $ci == true ]]; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + fi + + # LTTNG is the logging infrastructure used by Core CLR. Need this variable set + # so it doesn't output warnings to the console. + export LTTNG_HOME="$HOME" + + # Source Build uses DotNetCoreSdkDir variable + if [[ -n "${DotNetCoreSdkDir:-}" ]]; then + export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir" + fi + + # Find the first path on $PATH that contains the dotnet.exe + if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then + local dotnet_path=`command -v dotnet` + if [[ -n "$dotnet_path" ]]; then + ResolvePath "$dotnet_path" + export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` + fi + fi + + ReadGlobalVersion "dotnet" + local dotnet_sdk_version=$_ReadGlobalVersion + local dotnet_root="" + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + dotnet_root="$DOTNET_INSTALL_DIR" + else + dotnet_root="$repo_root/.dotnet" + + export DOTNET_INSTALL_DIR="$dotnet_root" + + if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + if [[ "$install" == true ]]; then + InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" + else + Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" + ExitWithExitCode 1 + fi + fi + fi + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + Write-PipelinePrependPath -path "$dotnet_root" + + Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" + Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" + + # return value + _InitializeDotNetCli="$dotnet_root" +} + +function InstallDotNetSdk { + local root=$1 + local version=$2 + local architecture="unset" + if [[ $# -ge 3 ]]; then + architecture=$3 + fi + InstallDotNet "$root" "$version" $architecture 'sdk' 'true' $runtime_source_feed $runtime_source_feed_key +} + +function InstallDotNet { + local root=$1 + local version=$2 + local runtime=$4 + + local dotnetVersionLabel="'$runtime v$version'" + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then + runtimePath="$root" + runtimePath="$runtimePath/shared" + case "$runtime" in + dotnet) + runtimePath="$runtimePath/Microsoft.NETCore.App" + ;; + aspnetcore) + runtimePath="$runtimePath/Microsoft.AspNetCore.App" + ;; + windowsdesktop) + runtimePath="$runtimePath/Microsoft.WindowsDesktop.App" + ;; + *) + ;; + esac + runtimePath="$runtimePath/$version" + + dotnetVersionLabel="runtime toolset '$runtime/$architecture v$version'" + + if [ -d "$runtimePath" ]; then + echo " Runtime toolset '$runtime/$architecture v$version' already installed." + local installSuccess=1 + return + fi + fi + + GetDotNetInstallScript "$root" + local install_script=$_GetDotNetInstallScript + + local installParameters=(--version $version --install-dir "$root") + + if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then + installParameters+=(--architecture $3) + fi + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then + installParameters+=(--runtime $4) + fi + if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then + installParameters+=(--skip-non-versioned-files) + fi + + local variations=() # list of variable names with parameter arrays in them + + local public_location=("${installParameters[@]}") + variations+=(public_location) + + local dotnetbuilds=("${installParameters[@]}" --azure-feed "https://ci.dot.net/public") + variations+=(dotnetbuilds) + + if [[ -n "${6:-}" ]]; then + variations+=(private_feed) + local private_feed=("${installParameters[@]}" --azure-feed $6) + if [[ -n "${7:-}" ]]; then + # The 'base64' binary on alpine uses '-d' and doesn't support '--decode' + # '-d'. To work around this, do a simple detection and switch the parameter + # accordingly. + decodeArg="--decode" + if base64 --help 2>&1 | grep -q "BusyBox"; then + decodeArg="-d" + fi + decodedFeedKey=`echo $7 | base64 $decodeArg` + private_feed+=(--feed-credential $decodedFeedKey) + fi + fi + + local installSuccess=0 + for variationName in "${variations[@]}"; do + local name="$variationName[@]" + local variation=("${!name}") + echo " Attempting to install $dotnetVersionLabel from $variationName." + bash "$install_script" "${variation[@]}" && installSuccess=1 + if [[ "$installSuccess" -eq 1 ]]; then + break + fi + + echo " Failed to install $dotnetVersionLabel from $variationName." + done + + if [[ "$installSuccess" -eq 0 ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install $dotnetVersionLabel from any of the specified locations." + ExitWithExitCode 1 + fi +} + +function with_retries { + local maxRetries=5 + local retries=1 + echo "Trying to run '$@' for maximum of $maxRetries attempts." + while [[ $((retries++)) -le $maxRetries ]]; do + "$@" + + if [[ $? == 0 ]]; then + echo "Ran '$@' successfully." + return 0 + fi + + timeout=$((3**$retries-1)) + echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 + sleep $timeout + done + + echo "Failed to execute '$@' for $maxRetries times." 1>&2 + + return 1 +} + +function GetDotNetInstallScript { + local root=$1 + local install_script="$root/dotnet-install.sh" + local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + + if [[ ! -a "$install_script" ]]; then + mkdir -p "$root" + + echo "Downloading '$install_script_url'" + + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + # first, try directly, if this fails we will retry with verbose logging + curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { + if command -v openssl &> /dev/null; then + echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation" + echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 + fi + echo "Will now retry the same URL with verbose logging." + with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + } + else + with_retries wget -v -O "$install_script" "$install_script_url" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + fi + fi + # return value + _GetDotNetInstallScript="$install_script" +} + +function InitializeBuildTool { + if [[ -n "${_InitializeBuildTool:-}" ]]; then + return + fi + + InitializeDotNetCli $restore + + # return values + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildToolCommand="msbuild" + # use override if it exists - commonly set by source-build + if [[ "${_OverrideArcadeInitializeBuildToolFramework:-x}" == "x" ]]; then + _InitializeBuildToolFramework="net8.0" + else + _InitializeBuildToolFramework="${_OverrideArcadeInitializeBuildToolFramework}" + fi +} + +# Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116 +function GetNuGetPackageCachePath { + if [[ -z ${NUGET_PACKAGES:-} ]]; then + if [[ "$use_global_nuget_cache" == true ]]; then + export NUGET_PACKAGES="$HOME/.nuget/packages" + else + export NUGET_PACKAGES="$repo_root/.packages" + export RESTORENOCACHE=true + fi + fi + + # return value + _GetNuGetPackageCachePath=$NUGET_PACKAGES +} + +function InitializeNativeTools() { + if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then + return + fi + if grep -Fq "native-tools" $global_json_file + then + local nativeArgs="" + if [[ "$ci" == true ]]; then + nativeArgs="--installDirectory $tools_dir" + fi + "$_script_dir/init-tools-native.sh" $nativeArgs + fi +} + +function InitializeToolset { + if [[ -n "${_InitializeToolset:-}" ]]; then + return + fi + + GetNuGetPackageCachePath + + ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" + + local toolset_version=$_ReadGlobalVersion + local toolset_location_file="$toolset_dir/$toolset_version.txt" + + if [[ -a "$toolset_location_file" ]]; then + local path=`cat "$toolset_location_file"` + if [[ -a "$path" ]]; then + # return value + _InitializeToolset="$path" + return + fi + fi + + if [[ "$restore" != true ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." + ExitWithExitCode 2 + fi + + local proj="$toolset_dir/restore.proj" + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:$log_dir/ToolsetRestore.binlog" + fi + + echo '' > "$proj" + MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" + + local toolset_build_proj=`cat "$toolset_location_file"` + + if [[ ! -a "$toolset_build_proj" ]]; then + Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" + ExitWithExitCode 3 + fi + + # return value + _InitializeToolset="$toolset_build_proj" +} + +function ExitWithExitCode { + if [[ "$ci" == true && "$prepare_machine" == true ]]; then + StopProcesses + fi + exit $1 +} + +function StopProcesses { + echo "Killing running build processes..." + pkill -9 "dotnet" || true + pkill -9 "vbcscompiler" || true + return 0 +} + +function MSBuild { + local args=$@ + if [[ "$pipelines_log" == true ]]; then + InitializeBuildTool + InitializeToolset + + if [[ "$ci" == true ]]; then + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" + fi + + local toolset_dir="${_InitializeToolset%/*}" + # new scripts need to work with old packages, so we need to look for the old names/versions + local selectedPath= + local possiblePaths=() + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp3.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/net7.0/Microsoft.DotNet.Arcade.Sdk.dll" ) + for path in "${possiblePaths[@]}"; do + if [[ -f $path ]]; then + selectedPath=$path + break + fi + done + if [[ -z "$selectedPath" ]]; then + Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." + ExitWithExitCode 1 + fi + args+=( "-logger:$selectedPath" ) + fi + + MSBuild-Core ${args[@]} +} + +function MSBuild-Core { + if [[ "$ci" == true ]]; then + if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." + ExitWithExitCode 1 + fi + + if [[ "$node_reuse" == true ]]; then + Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." + ExitWithExitCode 1 + fi + fi + + InitializeBuildTool + + local warnaserror_switch="" + if [[ $warn_as_error == true ]]; then + warnaserror_switch="/warnaserror" + fi + + function RunBuildTool { + export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" + + "$_InitializeBuildTool" "$@" || { + local exit_code=$? + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + echo "Build failed with exit code $exit_code. Check errors above." + + # When running on Azure Pipelines, override the returned exit code to avoid double logging. + if [[ "$ci" == "true" && -n ${SYSTEM_TEAMPROJECT:-} ]]; then + Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + else + ExitWithExitCode $exit_code + fi + } + } + + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" +} + +function GetDarc { + darc_path="$temp_dir/darc" + version="$1" + + if [[ -n "$version" ]]; then + version="--darcversion $version" + fi + + "$eng_root/common/darc-init.sh" --toolpath "$darc_path" $version +} + +ResolvePath "${BASH_SOURCE[0]}" +_script_dir=`dirname "$_ResolvePath"` + +. "$_script_dir/pipeline-logging-functions.sh" + +eng_root=`cd -P "$_script_dir/.." && pwd` +repo_root=`cd -P "$_script_dir/../.." && pwd` +repo_root="${repo_root}/" +artifacts_dir="${repo_root}artifacts" +toolset_dir="$artifacts_dir/toolset" +tools_dir="${repo_root}.tools" +log_dir="$artifacts_dir/log/$configuration" +temp_dir="$artifacts_dir/tmp/$configuration" + +global_json_file="${repo_root}global.json" +# determine if global.json contains a "runtimes" entry +global_json_has_runtimes=false +if command -v jq &> /dev/null; then + if jq -e '.tools | has("runtimes")' "$global_json_file" &> /dev/null; then + global_json_has_runtimes=true + fi +elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then + global_json_has_runtimes=true +fi + +# HOME may not be defined in some scenarios, but it is required by NuGet +if [[ -z $HOME ]]; then + export HOME="${repo_root}artifacts/.home/" + mkdir -p "$HOME" +fi + +mkdir -p "$toolset_dir" +mkdir -p "$temp_dir" +mkdir -p "$log_dir" + +Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir" +Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" +Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" +Write-PipelineSetVariable -name "Temp" -value "$temp_dir" +Write-PipelineSetVariable -name "TMP" -value "$temp_dir" + +# Import custom tools configuration, if present in the repo. +if [ -z "${disable_configure_toolset_import:-}" ]; then + configure_toolset_script="$eng_root/configure-toolset.sh" + if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" + fi +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi diff --git a/eng/resources/Directory.Build.props b/eng/resources/Directory.Build.props new file mode 100644 index 000000000..bb5b23d29 --- /dev/null +++ b/eng/resources/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/eng/resources/Directory.Build.targets b/eng/resources/Directory.Build.targets new file mode 100644 index 000000000..bb5b23d29 --- /dev/null +++ b/eng/resources/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/eng/targets/NuGet.targets b/eng/targets/NuGet.targets new file mode 100644 index 000000000..fb84e521f --- /dev/null +++ b/eng/targets/NuGet.targets @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props new file mode 100644 index 000000000..c38bf4fd4 --- /dev/null +++ b/eng/targets/Settings.props @@ -0,0 +1,10 @@ + + + + MIT + Microsoft + dotnet try + $(ArtifactsBinDir) + $(CopyrightMicrosoft) + + diff --git a/eng/targets/Versions.targets b/eng/targets/Versions.targets new file mode 100644 index 000000000..2a81f2358 --- /dev/null +++ b/eng/targets/Versions.targets @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eng/templates/build-and-test-job-linux-templates.yml b/eng/templates/build-and-test-job-linux-templates.yml new file mode 100644 index 000000000..b6760104d --- /dev/null +++ b/eng/templates/build-and-test-job-linux-templates.yml @@ -0,0 +1,41 @@ +# build-and-test-job-linux-template.yml +parameters: + buildConfig: 'Release' + buildArgs: '' + skipTests: false + additionalArgs: '' # Additional arguments to pass to the test command + +steps: +- template: /eng/templates/setup-template.yml@self + +- script: | + mkdir -p "$(Build.SourcesDirectory)/artifacts" + cp eng/resources/* "$(Build.SourcesDirectory)/artifacts" + displayName: Prevent test directory crawling + +- pwsh: | + $testArg = if ($env:SKIPTESTS -ne "true") { "--test" } else { "" } + Write-Host "##vso[task.setvariable variable=_TestArgs]$testArg" + displayName: Promote variables + +- script: $(Build.SourcesDirectory)/eng/cibuild.sh + --configuration ${{ parameters.buildConfig }} + --prepareMachine + $(_TestArgs) + displayName: Build + env: + POCKETLOGGER_LOG_PATH: $(PocketLoggerLogPath) + TRYDOTNET_PREBUILDS_PATH: $(TryDotNetPrebuildsPath) + +- script: | + dotnet test -l trx --no-build --blame-hang-timeout 15m --blame-hang-dump-type full -c ${{ parameters.buildConfig }} --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/${{ parameters.buildConfig }} ${{ parameters.additionalArgs }} + displayName: Test / Blame + workingDirectory: $(Build.SourcesDirectory) + condition: ne('${{ parameters.skipTests }}', 'true') + env: + RunIntegrationTests: $(RunIntegrationTests) + +- template: /eng/templates/delete-dump-files-template.yml@self + parameters: + buildConfig: ${{ parameters.buildConfig }} + skipTests: ${{ parameters.skipTests }} \ No newline at end of file diff --git a/eng/templates/build-and-test-job-windows-templates.yml b/eng/templates/build-and-test-job-windows-templates.yml new file mode 100644 index 000000000..625ffc101 --- /dev/null +++ b/eng/templates/build-and-test-job-windows-templates.yml @@ -0,0 +1,45 @@ +# build-and-test-job-windows-template.yml +parameters: + buildConfig: 'Release' # Pass build configuration as a parameter + buildArgs: '' # Pass additional arguments to the build script + skipTests: false # Default is not skipping tests + additionalArgs: '' # Additional arguments to pass to the test command + +steps: +- template: /eng/templates/setup-template.yml@self + +- script: | + robocopy "eng\resources" "$(Build.SourcesDirectory)\artifacts" + :: robocopy return codes are terrible; 1 means files were copied + if "%errorlevel%" == "1" exit /b 0 + exit /b 1 + displayName: Prevent test directory crawling + +- pwsh: | + $testArg = if ($env:SKIPTESTS -ne "true") { "-test" } else { "" } + Write-Host "##vso[task.setvariable variable=_TestArgs]$testArg" + displayName: Promote variables + +- script: $(Build.SourcesDirectory)\eng\CIBuild.cmd + -configuration ${{ parameters.buildConfig }} + -prepareMachine + -sign + ${{ parameters.buildArgs }} + $(_TestArgs) + displayName: Build + env: + POCKETLOGGER_LOG_PATH: $(PocketLoggerLogPath) + TRYDOTNET_PREBUILDS_PATH: $(TryDotNetPrebuildsPath) + +- script: | + dotnet test -l trx --no-build --blame-hang-timeout 15m --blame-hang-dump-type full -c ${{ parameters.buildConfig }} --results-directory $(Build.SourcesDirectory)/artifacts/TestResults/${{ parameters.buildConfig }} ${{ parameters.additionalArgs }} + displayName: Test / Blame + workingDirectory: $(Build.SourcesDirectory) + condition: ne('${{ parameters.skipTests }}', 'true') + env: + RunIntegrationTests: $(RunIntegrationTests) + +- template: /eng/templates/delete-dump-files-template.yml@self + parameters: + buildConfig: ${{ parameters.buildConfig }} + skipTests: ${{ parameters.skipTests }} \ No newline at end of file diff --git a/eng/templates/delete-dump-files-template.yml b/eng/templates/delete-dump-files-template.yml new file mode 100644 index 000000000..89ebc2c54 --- /dev/null +++ b/eng/templates/delete-dump-files-template.yml @@ -0,0 +1,10 @@ +# delete-dump-files-template.yml +parameters: + buildConfig: '' + skipTests: false + +steps: +- pwsh: Get-ChildItem *.dmp -Recurse | Remove-Item + displayName: Delete dump files + workingDirectory: $(Build.SourcesDirectory)/artifacts/TestResults/${{ parameters.buildConfig }} + condition: and(ne(variables['KeepDumps'], 'true'), ne('${{ parameters.skipTests }}', 'true')) \ No newline at end of file diff --git a/eng/templates/es-metadata.yml b/eng/templates/es-metadata.yml new file mode 100644 index 000000000..97cac8c05 --- /dev/null +++ b/eng/templates/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: ab4d58de-beb3-4714-866c-1b46a48ea002 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\NET Tools\TryDotNET\trydotnet diff --git a/eng/templates/setup-template.yml b/eng/templates/setup-template.yml new file mode 100644 index 000000000..a08b4e8aa --- /dev/null +++ b/eng/templates/setup-template.yml @@ -0,0 +1,19 @@ +# setup-template.yml +steps: +- script: git config --global core.longpaths true + displayName: Enable `git clean` to handle long paths + +- checkout: self + clean: true + +- task: NodeTool@0 + displayName: Add NodeJS/npm + inputs: + versionSpec: $(NodeJSVersion) + +- task: UseDotNet@2 + displayName: Add dotnet + inputs: + packageType: sdk + version: $(DotNetSdkVersion) + installationPath: $(Agent.ToolsDirectory)/dotnet diff --git a/eng/templates/variables-template.yml b/eng/templates/variables-template.yml new file mode 100644 index 000000000..72ec112f9 --- /dev/null +++ b/eng/templates/variables-template.yml @@ -0,0 +1,17 @@ +# variables-template.yml + +variables: + - name: _TeamName + value: DotNetTry + - name: _BuildConfig + value: Release + - name: _PublishUsingPipelines + value: true + - name: _DotNetArtifactsCategory + value: .NETCore + - name: DotNetSdkVersion + value: '8.0.204' + - name: NodeJSVersion + value: '16.13.0' + - name: Codeql.Enabled + value: true \ No newline at end of file diff --git a/eng/tests/UpToDate.ps1 b/eng/tests/UpToDate.ps1 new file mode 100644 index 000000000..02f4ee13b --- /dev/null +++ b/eng/tests/UpToDate.ps1 @@ -0,0 +1,68 @@ +# This script verifies that subsequent calls to `build.cmd` don't cause js/css to be unnecessarily rebuilt. + +[CmdletBinding(PositionalBinding=$false)] +param ( + [string][Alias('c')]$configuration = "Debug", + [parameter(ValueFromRemainingArguments=$true)][string[]]$properties +) + +Set-StrictMode -version 2.0 +$ErrorActionPreference = "Stop" + +try { + $RepoRoot = Join-Path $PSScriptRoot ".." | Join-Path -ChildPath ".." -Resolve + $BuildScript = Join-Path $RepoRoot "build.cmd" + + # do first build + & $BuildScript -configuration $configuration @properties + if ($LASTEXITCODE -ne 0) { + Write-Host "Error running first build." + exit 1 + } + + # list of sentinel files to check + $FileList = Get-ChildItem -Path "$RepoRoot\MLS.Agent\wwwroot" -Recurse -File | %{ $_.FullName } + + # gather file timestamps + $InitialFilesAndTimes = @{} + foreach ($f in $FileList) { + $LastWriteTime = (Get-Item $f).LastWriteTimeUtc + $InitialFilesAndTimes.Add($f, $LastWriteTime) + } + + # build again + & $BuildScript -configuration $configuration @properties + if ($LASTEXITCODE -ne 0) { + Write-Host "Error running second build." + exit 1 + } + + # gather file timestamps again + $FinalFilesAndTimes = @{} + foreach ($f in $FileList) { + $LastWriteTime = (Get-Item $f).LastWriteTimeUtc + $FinalFilesAndTimes.Add($f, $LastWriteTime) + } + + # validate that file timestamps haven't changed + $RebuiltFiles = @() + foreach ($f in $FileList) { + $InitialTime = $InitialFilesAndTimes[$f] + $FinalTime = $FinalFilesAndTimes[$f] + if ($InitialTime -ne $FinalTime) { + $RebuiltFiles += $f + } + } + + $FileCount = $FileList.Length + $RebuiltCount = $RebuiltFiles.Length + Write-Host "$RebuiltCount of $FileCount files were re-built." + $RebuiltFiles | ForEach-Object { Write-Host " $_" } + exit $RebuiltCount +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 +} diff --git a/es-metadata.yml b/es-metadata.yml new file mode 100644 index 000000000..97cac8c05 --- /dev/null +++ b/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: ab4d58de-beb3-4714-866c-1b46a48ea002 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\NET Tools\TryDotNET\trydotnet diff --git a/global.json b/global.json new file mode 100644 index 000000000..02e68dde0 --- /dev/null +++ b/global.json @@ -0,0 +1,14 @@ +{ + "sdk": { + "version": "9.0.111", + "allowPrerelease": true, + "rollForward": "latestMinor" + }, + "tools": { + "dotnet": "9.0.111", + "rollForward": "latestMinor" + }, + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.25263.4" + } +} diff --git a/restore.cmd b/restore.cmd new file mode 100644 index 000000000..ad10e5790 --- /dev/null +++ b/restore.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -noprofile -executionPolicy RemoteSigned -file "%~dp0eng\build.ps1" -restore -binaryLog %* diff --git a/restore.sh b/restore.sh new file mode 100755 index 000000000..cf80bdd6a --- /dev/null +++ b/restore.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd)" + +. "$scriptroot/eng/build.sh" --restore --binaryLog "$@" diff --git a/src/Microsoft.TryDotNet.IntegrationTests/(Pocket)/Logger/Class1.cs b/src/Microsoft.TryDotNet.IntegrationTests/(Pocket)/Logger/Class1.cs new file mode 100644 index 000000000..3495494c6 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/(Pocket)/Logger/Class1.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.CSharpProject; +using Microsoft.DotNet.Interactive.Documents; +using Microsoft.TryDotNet.IntegrationTests; +using Xunit.Abstractions; + +namespace Pocket; + +internal partial class LogEvents +{ + public static IDisposable SubscribeToPocketLogger(this ITestOutputHelper output) => + Subscribe( + e => output.WriteLine(e.ToLogString()), + AssembliesPublishingPocketLoggerEvents); + + public static Assembly[] AssembliesPublishingPocketLoggerEvents => + [ + typeof(Microsoft.TryDotNet.Program).Assembly, // Microsoft.TryDotNet.dll + typeof(Kernel).Assembly, // Microsoft.DotNet.Interactive.dll + typeof(CSharpProjectKernel).Assembly, // Microsoft.DotNet.Interactive.CSharpProject.dll + typeof(InteractiveDocument).Assembly, // Microsoft.DotNet.Interactive.Documents.dll, + typeof(IntegratedServicesFixture).Assembly // Microsoft.TryDotNet.IntegrationTests.dll + ]; +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/AsyncLazy.cs b/src/Microsoft.TryDotNet.IntegrationTests/AsyncLazy.cs new file mode 100644 index 000000000..df8efa1dd --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/AsyncLazy.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal class AsyncLazy +{ + private readonly Lazy> _lazy; + + public AsyncLazy(Func> initialize) + { + if (initialize is null) + { + throw new ArgumentNullException(nameof(initialize)); + } + +#pragma warning disable VSTHRD011 // Use AsyncLazy + _lazy = new Lazy>(initialize); +#pragma warning restore VSTHRD011 // Use AsyncLazy + } + + public Task ValueAsync() => _lazy.Value; +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/CollectionDefinitionForIntegratedServicesFixture.cs b/src/Microsoft.TryDotNet.IntegrationTests/CollectionDefinitionForIntegratedServicesFixture.cs new file mode 100644 index 000000000..0002158a3 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/CollectionDefinitionForIntegratedServicesFixture.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace Microsoft.TryDotNet.IntegrationTests; + +[CollectionDefinition(nameof(IntegratedServicesFixture), DisableParallelization = true)] +public class CollectionDefinitionForIntegratedServicesFixture : ICollectionFixture +{ + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/CommandLine.cs b/src/Microsoft.TryDotNet.IntegrationTests/CommandLine.cs new file mode 100644 index 000000000..b911a308f --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/CommandLine.cs @@ -0,0 +1,209 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.Utility; +using Pocket; +using CommandLineInvocationException = Microsoft.TryDotNet.IntegrationTests.CommandLineInvocationException; + +public static class CommandLine +{ + public static Task Execute( + FileInfo exePath, + string args, + DirectoryInfo? workingDir = null, + TimeSpan? timeout = null) => + Execute(exePath.FullName, + args, + workingDir, + timeout); + + + public static async Task Execute( + string command, + string args, + DirectoryInfo? workingDir = null, + TimeSpan? timeout = null) + { + args ??= ""; + + var stdOut = new StringBuilder(); + var stdErr = new StringBuilder(); + + using (var operation = ConfirmOnExit(command, args)) + using (var process = StartProcess( + command, + args, + workingDir, + output: data => + { + stdOut.AppendLine(data); + }, + error: data => + { + stdErr.AppendLine(data); + })) + { + var exitCode = await process.Complete().Timeout(timeout ?? TimeSpan.FromMinutes(1)); + + var output = stdOut.Replace("\r\n", "\n").ToString().Split('\n'); + + var error = stdErr.Replace("\r\n", "\n").ToString().Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if (error.All(string.IsNullOrWhiteSpace)) + { + error = null; + } + + var result = new CommandLineResult( + exitCode: exitCode, + output: output, + error: error); + + if (exitCode == 0) + { + operation.Succeed( + "> {command} {args} -> exited with {code}", + process.StartInfo.FileName, + process.StartInfo.Arguments, + process.ExitCode); + } + else + { + operation.Fail(new CommandLineInvocationException(result)); + } + + return result; + } + } + + public static async Task Complete( + this Process process) => + await Task.Run(() => + { + process.WaitForExit(); + + return process.ExitCode; + }); + + public static async Task Timeout( + this Task source, + TimeSpan timeout, + string? message = null) + { + if (await Task.WhenAny( + source, + Task.Delay(timeout)) != source) + { + throw string.IsNullOrWhiteSpace(message) ? new TimeoutException(): new TimeoutException(message); + } + + return await source; + } + + public static async Task Timeout( + this Task source, + TimeSpan timeout, + string? message = null) + { + if (await Task.WhenAny( + source, + Task.Delay(timeout)) != source) + { + throw string.IsNullOrWhiteSpace(message) ? new TimeoutException() : new TimeoutException(message); + } + + await source; + } + + public static Process StartProcess( + string command, + string args, + DirectoryInfo? workingDir, + Action? output = null, + Action? error = null, + params (string key, string value)[] environmentVariables) + { + using (var operation = Log.OnEnterAndExit()) + { + args ??= ""; + + var process = new Process + { + StartInfo = + { + Arguments = args, + FileName = command, + RedirectStandardError = true, + RedirectStandardOutput = true, + RedirectStandardInput = true, + WorkingDirectory = workingDir?.FullName ?? string.Empty, + StandardOutputEncoding = Encoding.UTF8 + } + }; + + operation.Info("> {process} {args}", command, args); + + if (environmentVariables?.Length > 0) + { + foreach (var tuple in environmentVariables) + { + operation.Trace("Adding environment variable {tuple}", tuple); + process.StartInfo.Environment.Add(tuple.key, tuple.value); + } + } + + if (output is not null) + { + process.OutputDataReceived += (sender, eventArgs) => + { + if (eventArgs.Data is not null) + { + output(eventArgs.Data); + } + }; + } + + if (error is not null) + { + process.ErrorDataReceived += (sender, eventArgs) => + { + if (eventArgs.Data is not null) + { + error(eventArgs.Data); + } + }; + } + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + return process; + } + } + + public static string AppendArgs(this string initial, string? append = null) => + string.IsNullOrWhiteSpace(append) + ? initial + : $"{initial} {append}"; + + private static ConfirmationLogger ConfirmOnExit( + object command, + string args, + [CallerMemberName] string? operationName = null) + { + return new ConfirmationLogger( + operationName: operationName ?? "", + category: Log.Category, + message: "> {command} {args}", + args: [command, args], + logOnStart: true); + } + + private static Logger Log { get; } = new Logger(nameof(CommandLine)); +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/CommandLineInvocationException.cs b/src/Microsoft.TryDotNet.IntegrationTests/CommandLineInvocationException.cs new file mode 100644 index 000000000..80118cee9 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/CommandLineInvocationException.cs @@ -0,0 +1,12 @@ +using System; +using Microsoft.DotNet.Interactive.Utility; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class CommandLineInvocationException : Exception +{ + public CommandLineInvocationException(CommandLineResult result, string? message = null) : base( + $"{message}{Environment.NewLine}Exit code {result.ExitCode}: {string.Join("\n", result.Error)}".Trim()) + { + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/DotNetOnline.cs b/src/Microsoft.TryDotNet.IntegrationTests/DotNetOnline.cs new file mode 100644 index 000000000..a24320229 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/DotNetOnline.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; +using Microsoft.Playwright; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class DotNetOnline +{ + private readonly IPage _page; + + public DotNetOnline(IPage page) + { + _page = page; + } + + public Task FocusAsync() + { + return _page.EvaluateAsync("() => { dotnetOnline.focus(); }"); + } + + public Task ExecuteAsync() + { + return _page.EvaluateAsync("() => { dotnetOnline.execute(); }"); + } + + public Task SetCodeAsync(string code) + { + return _page.EvaluateAsync("(code) => { dotnetOnline.setCode(code); }",code); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs new file mode 100644 index 000000000..acd19df65 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs @@ -0,0 +1,722 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +using FluentAssertions; +using Microsoft.Playwright; +using Pocket.For.Xunit; +using Xunit.Abstractions; + +namespace Microsoft.TryDotNet.IntegrationTests; + +[LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +public class EditorTests : PlaywrightTestBase +{ + public EditorTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output) + { + } + + [IntegrationTestFact] + public async Task can_load_monaco_editor() + { + var page = await NewPageAsync(); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var isVisible = await page.Locator("div[role = \"code\"]").IsVisibleAsync(); + + await page.TestScreenShotAsync(); + isVisible.Should().BeTrue(); + + } + + [IntegrationTestFact] + public async Task can_load_the_wasm_runner() + { + var wasmRunnerLoaded = false; + var page = await NewPageAsync(); + + await page.RouteAsync("**/*", async route => + { + if (route.Request.Url.Contains("blazor.webassembly.js")) + { + wasmRunnerLoaded = true; + } + + await route.ContinueAsync(); + }); + + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator("div[role = \"code\"]").IsVisibleAsync(); + + await page.TestScreenShotAsync(); + + wasmRunnerLoaded.Should().BeTrue(); + } + + [IntegrationTestFact] + public async Task wasm_runner_is_not_visible_to_screenReaders() + { + var page = await NewPageAsync(); + + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator("div[role = \"code\"]").IsVisibleAsync(); + + await page.TestScreenShotAsync(); + + var runner = page.Locator(@"[role = ""wasm-runner""]"); + var ariaAttribute = await runner.GetAttributeAsync("aria-hidden"); + ariaAttribute.Should().Be("true"); + + } + + [IntegrationTestFact] + public async Task wasm_runner_is_not_part_of_tab_navigation() + { + var page = await NewPageAsync(); + + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator("div[role = \"code\"]").IsVisibleAsync(); + + await page.TestScreenShotAsync(); + + var runner = page.Locator(@"[role = ""wasm-runner""]"); + var ariaAttribute = await runner.GetAttributeAsync("tabindex"); + ariaAttribute.Should().Be("-1"); + + } + + + [IntegrationTestFact] + public async Task notifies_when_editor_is_ready() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + + var readyAwaiter = interceptor.AwaitForMessage("HostEditorReady"); + + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var found = await readyAwaiter; + + await page.TestScreenShotAsync(); + found.Should().NotBeNull(); + } + + [IntegrationTestFact] + public async Task can_open_project() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = "Console.WriteLine(\"New Project\")" + } + } + } + }); + + await page.TestScreenShotAsync(); + var projectLoaded = await projectLoadedAwaiter; + projectLoaded.Should().NotBeNull(); + } + + [IntegrationTestFact] + public async Task can_open_document() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var randomValue = Guid.NewGuid().ToString("N"); + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = $@"Console.WriteLine(""{randomValue}"");" + } + } + } + }); + var projectLoaded = await projectLoadedAwaiter; + projectLoaded.Should().NotBeNull(); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + + + var documentOpened = await documentOpenedAwaiter; + documentOpened.Should().NotBeNull(); + await page.TestScreenShotAsync(); + var text = await page.GetEditorContentAsync(); + text.Should().Contain(randomValue); + } + + [IntegrationTestFact] + public async Task can_open_document_and_populate_editor_from_region() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "program.cs", + content = "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\n\nnamespace Program\n{\n class Program\n {\n static void Main(string[] args)\n {\n #region controller\nConsole.WriteLine(123);\n #endregion\n }\n }\n}" + } + } + } + }); + var projectLoaded = await projectLoadedAwaiter; + projectLoaded.Should().NotBeNull(); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "program.cs", + regionName ="controller" + }); + + + var documentOpened = await documentOpenedAwaiter; + documentOpened.Should().NotBeNull(); + await page.TestScreenShotAsync(); + var text = await page.GetEditorContentAsync(); + text.Should().Contain("Console.WriteLine(123);"); + } + + [IntegrationTestFact] + public async Task minimap_is_not_visible() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.TestScreenShotAsync(); + var minimap = page.Locator("div.minimap"); + var isHidden = await minimap.IsHiddenAsync(); + isHidden.Should().BeTrue(); + } + + [IntegrationTestFact] + public async Task can_show_minimap() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.DispatchMessage(new + { + type = "ConfigureMonacoEditor", + editorOptions = new + { + minimap = new + { + enabled = true + } + } + }); + var minimap = page.Locator("div.minimap"); + await page.TestScreenShotAsync(); + var isVisible = await minimap.IsVisibleAsync(); + isVisible.Should().BeTrue(); + } + + [IntegrationTestFact] + public async Task can_configure_theme() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.DispatchMessage(new + { + type = "ConfigureMonacoEditor", + theme = "vs-dark" + }); + + var editor = page.Locator("div[role = \"code\"]"); + await page.TestScreenShotAsync(); + + var classAttribute = await editor.GetAttributeAsync("class"); + classAttribute.Should().Contain("vs-dark"); + } + + [IntegrationTestFact] + [Obsolete] + public async Task when_user_code_in_editor_diagnostics_are_produced() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + var editor = await page.FindEditor(); + await editor.FocusAsync(); + + await page.RunAndWaitForConsoleMessageAsync(async () => + { + await editor.TypeAsync(@"///////////////////////// +int i = ""NaN""; +/////////////////////////".Replace("\r\n", "\n")); + + await editor.PressAsync("Enter", new LocatorPressOptions { Delay = 0.5f }); + + }, new PageRunAndWaitForConsoleMessageOptions() + { + Predicate = message => message.Text.Contains("[MonacoEditorAdapter.setMarkers]"), + Timeout = Debugger.IsAttached ? 0.0f : (float)TimeSpan.FromMinutes(10).TotalMilliseconds + }); + + await Task.Delay(TimeSpan.FromSeconds(1)); + + var diagnosticMarker = page.Locator("div .squiggly-error"); + await diagnosticMarker.IsVisibleAsync(); + await page.TestScreenShotAsync(); + + var markerElementJson = await page.EvaluateAsync("JSON.stringify(trydotnetEditor.getEditor().getMarkers())"); + var markers = JsonSerializer.Deserialize(markerElementJson); + var expected = new EditorMarker(8, "Program.cs(2,9): error CS0029: Cannot implicitly convert type 'string' to 'int'", 2, 9, 2, 14); + markers.Should().ContainSingle(m => m == expected); + } + + [IntegrationTestFact] + [Obsolete] + public async Task when_user_code_in_editor_is_executed_display_events_are_produced() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + var randomValue = Guid.NewGuid().ToString("N"); + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor($@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.WriteLine(""{randomValue}"");".Replace("\r\n", "\n")); + + await page.TestScreenShotAsync(); + var messages = await page.RequestRunAsync(interceptor); + + + messages.Should().ContainSingle(e => e.GetProperty("type").GetString() == "StandardOutputValueProduced") + .Which + .GetProperty("event") + .GetProperty("formattedValues") + .GetRawText() + .Should() + .Contain(randomValue); + } + + [IntegrationTestFact(Skip ="Need to investigate. Disabling this test now to avoid blocking pipeline")] + [Obsolete] + public async Task user_typing_code_gets_completion() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor(@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.".Replace("\r\n", "\n")); + + await page.WaitForSelectorAsync(".monaco-list-row", new PageWaitForSelectorOptions + { + Timeout = (float)(TimeSpan.FromMinutes(10).TotalMilliseconds) + }); + + var rows = await page.QuerySelectorAllAsync(".monaco-list-row"); + + await page.TestScreenShotAsync(); + + var completionItemDisplayText = await Task.WhenAll(rows.Select(r => r.InnerTextAsync())); + + completionItemDisplayText.Should().Contain(new[] { "BackgroundColor", "Beep", "Clear" }); + } + + [IntegrationTestFact] + [Obsolete] + public async Task user_typing_code_gets_signatureHelp() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor($@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.WriteLine(".Replace("\r\n", "\n")); + + + await page.WaitForSelectorAsync(".parameter-hints-widget", new PageWaitForSelectorOptions + { + Timeout = (float)(TimeSpan.FromMinutes(10).TotalMilliseconds) + }); + + var parameterHint = await page.QuerySelectorAsync(".parameter-hints-widget"); + + await page.TestScreenShotAsync(); + + var signatureHelpDisplayText = await parameterHint!.InnerTextAsync(); + signatureHelpDisplayText = signatureHelpDisplayText.Replace("\r", ""); + + signatureHelpDisplayText.Should().Be(@" +01/19 +void Console.WriteLine() + +Writes the current line terminator to the standard output stream. +".Trim().Replace("\r", "")); + } + + [IntegrationTestFact] + [Obsolete] + public async Task when_user_code_in_editor_is_executed_it_produces_runResult_event() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + var randomValue = Guid.NewGuid().ToString("N"); + + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor($@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.WriteLine(""{randomValue}"");".Replace("\r\n", "\n")); + + await page.TestScreenShotAsync(); + var messages = await page.RequestRunAsync(interceptor); + + + messages.Should().ContainSingle(e => e.GetProperty("type").GetString() == "RunCompleted") + .Which + .GetProperty("outcome") + .GetRawText() + .Should() + .Contain("Success"); + } + + [IntegrationTestFact] + [Obsolete] + public async Task when_user_code_in_editor_is_executed_it_produces_runResult_event_with_outputs() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + var randomValue = Guid.NewGuid().ToString("N"); + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor($@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.WriteLine(""{randomValue}""); +Console.WriteLine(""{randomValue}a""); +Console.WriteLine(""{randomValue}b"");".Replace("\r\n", "\n")); + + await page.TestScreenShotAsync(); + var messages = await page.RequestRunAsync(interceptor); + + + messages.Should().ContainSingle(e => e.GetProperty("type").GetString() == "RunCompleted") + .Which + .GetProperty("output") + .GetRawText() + .Should() + .Contain($"{randomValue}\\n{randomValue}a\\n{randomValue}b\\n"); + } + + [IntegrationTestFact] + [Obsolete] + public async Task user_code_in_editor_is_executed() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + await page.GotoAsync((await Services.GetTryDotNetServerAsync()).Url + "editor?enableLogging=true"); + + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var projectLoadedAwaiter = interceptor.AwaitForMessage("ProjectOpened"); + var documentOpenedAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + + var randomValue = Guid.NewGuid().ToString("N"); + await page.DispatchMessage(new + { + type = "OpenProject", + project = new + { + files = new[] + { + new { + relativeFilePath = "Program.cs", + content = @"Console.WriteLine(""Hello World"");" + } + } + } + }); + + await projectLoadedAwaiter; + await page.DispatchMessage(new + { + type = "OpenDocument", + relativeFilePath = "Program.cs" + }); + + await documentOpenedAwaiter; + await page.ClearMonacoEditor(); + + await page.TypeTextInMonacoEditor($@"using System; +namespace myApp {{ +class Program {{ +static void Main() {{ +Console.WriteLine(""{randomValue}"");".Replace("\r\n", "\n")); + + await page.TestScreenShotAsync(); + var messages = await page.RequestRunAsync(interceptor); + + + messages.Should().ContainSingle(e => e.GetProperty("type").GetString() == "CommandSucceeded") + .Which + .GetProperty("command") + .GetProperty("command") + .GetProperty("code") + .GetRawText() + .Should() + .Contain(randomValue); + } + + private record EditorMarker(int severity, string message, int startLineNumber, int startColumn, int endLineNumber, int endColumn); +} diff --git a/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs b/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs new file mode 100644 index 000000000..c18d3d969 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.CSharpProject.Build; +using Pocket; +using Xunit; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class IntegratedServicesFixture : IAsyncLifetime +{ + private static readonly CompositeDisposable _disposables = new(); + + private static readonly AsyncLazy _lazyTryDotNetServer; + private static readonly AsyncLazy _lazyLearnServer; + private static readonly AsyncLazy _lazyPlaywright; + private static int _instanceCount; + + static IntegratedServicesFixture() + { + _lazyTryDotNetServer = new AsyncLazy(StartTryDotNetServer); + _lazyLearnServer = new AsyncLazy(StartLearnServer); + _lazyPlaywright = new AsyncLazy(StartPlaywright); + } + + public async Task InitializeAsync() + { + var consolePrebuild = await Prebuild.GetOrCreateConsolePrebuildAsync(true); + await consolePrebuild.EnsureReadyAsync(); + } + + public IntegratedServicesFixture() + { + Interlocked.Increment(ref _instanceCount); + } + + private static async Task StartLearnServer() + { + var server = await LearnServer.StartAsync(); + _disposables.Add(server); + return server; + } + + private static async Task StartTryDotNetServer() + { + var server = await TryDotNetServer.StartAsync(); + _disposables.Add(server); + return server; + } + + private static async Task StartPlaywright() + { + var server = await PlaywrightSession.StartAsync(); + _disposables.Add(server); + return server; + } + + public Task GetTryDotNetServerAsync() => _lazyTryDotNetServer.ValueAsync(); + + public Task GetLearnServerAsync() => _lazyLearnServer.ValueAsync(); + + public Task GetPlaywrightAsync() => _lazyPlaywright.ValueAsync(); + + public Task DisposeAsync() + { + _disposables.Dispose(); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs b/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs new file mode 100644 index 000000000..995adfc48 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +using Xunit; + +namespace Microsoft.TryDotNet.IntegrationTests +{ + internal class IntegrationTestFactAttribute : FactAttribute + { + private const string EnvironmentVariableName = "RunIntegrationTests"; + + public IntegrationTestFactAttribute(string? skipReason = null) + { + var variableValue = Environment.GetEnvironmentVariable(EnvironmentVariableName) ?? "false"; + switch (variableValue.ToLowerInvariant()) + { + case "1": + case "true": + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Skip = string.IsNullOrWhiteSpace(skipReason) ? "Ignored on Linux" : skipReason; + } + break; + default: + Skip = $"Skipping integration tests because environment variable '{EnvironmentVariableName}' was not 'true' or '1'."; + break; + } + } + } +} diff --git a/src/Microsoft.TryDotNet.IntegrationTests/LearnServer.cs b/src/Microsoft.TryDotNet.IntegrationTests/LearnServer.cs new file mode 100644 index 000000000..70d54723d --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/LearnServer.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Pocket; +using static Pocket.Logger; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class LearnServer : IDisposable +{ + private Process? _process; + + private LearnServer(Process process, Uri url) + { + _process = process; + Url = url; + } + + public Uri Url { get; } + + public static async Task StartAsync() + { + using var operation = Log.OnEnterAndConfirmOnExit(); + + var uriFound = false; + var allOutput = new StringBuilder(); + var completionSource = new TaskCompletionSource(); + + var buffer = new StringBuilder(); + + var process = CommandLine.StartProcess( + "pwsh", + "-c npx http-server --cors", + new DirectoryInfo(BuildProperties.LearnMockSitePath), + output => + { + if (!uriFound) + { + buffer.AppendLine(output); + var allText = buffer.ToString(); + var match = Regex.Match(allText, @".*(?http://\d+\.\d+\.\d+\.\d+:\d+).*", + RegexOptions.Multiline); + if (match.Success) + { + if (Uri.TryCreate(match.Groups["uri"].Value, UriKind.Absolute, out var uri)) + { + uriFound = true; + completionSource.SetResult(uri.ToLocalHost()); + } + } + } + }, + error: error => + { + var outputAndError = $""" + {allOutput} + + ERROR: + ------ + {error} + """; + + operation.Fail(message: error); + + completionSource.TrySetException(new Exception(outputAndError)); + }); + + operation.Info(("Process ID", process.Id), ("Process Name", process.ProcessName)); + + var timeout = Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(1); + + var url = await completionSource.Task.Timeout(timeout, $"node process not ready within {timeout.TotalSeconds}s. Output =\n{allOutput})"); + + operation.Succeed(); + + return new LearnServer(process, url); + } + + public void Dispose() + { + if (_process is { } process) + { + using var operation = Log.OnEnterAndConfirmOnExit(arg: ("Process ID", process.Id)); + _process = null; + process.Kill(true); + process.Dispose(); + operation.Succeed(); + } + } +} diff --git a/src/Microsoft.TryDotNet.IntegrationTests/MessageInterceptor.cs b/src/Microsoft.TryDotNet.IntegrationTests/MessageInterceptor.cs new file mode 100644 index 000000000..383f226bd --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/MessageInterceptor.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Playwright; + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal class MessageInterceptor +{ + private readonly ConcurrentDictionary> _completionSources = new (StringComparer.OrdinalIgnoreCase); + public List Messages { get; } = new(); + + public async Task InstallAsync(IPage page) + { + await page.ExposeFunctionAsync("postMessageLogger", async (JsonElement message) => + { + await Task.Yield(); + Messages.Add(message); + if (message.TryGetProperty("type", out var typeProperty)) + { + var messageType = typeProperty.GetString(); + if (messageType is not null) + { + if (_completionSources.TryRemove(messageType, out var cs)) + { + cs.SetResult(message); + } + } + } + + }); + } + + public Task AwaitForMessage(string messageType, TimeSpan? timeOut = null) + { + var cs = _completionSources.GetOrAdd(messageType, _ => new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)); + + if (Debugger.IsAttached) + { + return cs.Task; + } + + return cs.Task.Timeout(timeOut ?? TimeSpan.FromMinutes(1), $"Timeout waiting for message of type {messageType}"); + } +} + diff --git a/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj new file mode 100644 index 000000000..dd09f85f8 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj @@ -0,0 +1,77 @@ + + + + net9.0 + enable + false + GeneratedLocation.cs + $(MSBuildThisFileDirectory)bin\$(Configuration)\tool + false + embedded + + + + + + + + + all + + + all + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.v3.ncrunchproject b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.v3.ncrunchproject new file mode 100644 index 000000000..319cd523c --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PageExtensions.cs b/src/Microsoft.TryDotNet.IntegrationTests/PageExtensions.cs new file mode 100644 index 000000000..eb06405b6 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/PageExtensions.cs @@ -0,0 +1,168 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading.Tasks; + +using Microsoft.Playwright; + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal static class PageExtensions +{ + public static Task TestScreenShotAsync(this IPage page, string? label = null, [CallerMemberName] string? testName = null, [CallerFilePath] string? sourceFilePath = null) + { + var imageName = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}_{testName}"; + if (!string.IsNullOrWhiteSpace(label)) + { + imageName = $"{imageName}_{label}"; + } + return page.ScreenshotAsync(new PageScreenshotOptions + { + Path = Path.Combine("playwright_screenshots", $"screenshot_{imageName}.png"), + FullPage = true + }); + } + + public static async Task DispatchMessage(this IPage page, T data) + { + await page.EvaluateAsync(@"(request) => { +window.dispatchEvent(new MessageEvent(""message"", { data: request })); +}", data); + } + + public static async Task FindEditor(this IPage page) + { + var editor = page.Locator(@"textarea[role = ""textbox""]"); + await editor.IsVisibleAsync(); + return editor; + } + + public static async Task FindEditorContent(this IPage page) + { + var editor = page.Locator(@"div[role = ""presentation""] .view-lines"); + await editor.IsVisibleAsync(); + return editor; + } + + public static async Task FindEditorContent(this ILocator locator) + { + var editor = locator.Locator(@"div[role = ""presentation""] .view-lines"); + await editor.IsVisibleAsync(); + return editor; + } + public static async Task FindEditorContent(this IFrameLocator iframe) + { + var editor = iframe.Locator(@"div[role = ""presentation""] .view-lines"); + await editor.IsVisibleAsync(); + return editor; + } + + [Obsolete] + public static async Task TypeTextInMonacoEditor(this IPage page, string text, float? delay = null) + { + var editor = await page.FindEditor(); + await editor.FocusAsync(); + if (delay is not null) + { + await editor.TypeAsync(text, new LocatorTypeOptions {Delay = delay}); + } + else + { + await editor.TypeAsync(text); + } + } + + public static async Task GetEditorContentAsync(this IPage page) + { + var editor = await page.FindEditorContent(); + var text = await editor.TextContentAsync()?? string.Empty; + return text; + } + + public static async Task GetEditorContentAsync(this ILocator locator) + { + var editor = await locator.FindEditorContent(); + var text = await editor.TextContentAsync() ?? string.Empty; + return text; + } + + public static async Task GetEditorContentAsync(this IFrameLocator iframe) + { + var editor = await iframe.FindEditorContent(); + var text = await editor.TextContentAsync() ?? string.Empty; + return text; + } + + public static async Task ClearMonacoEditor(this IPage page) + { + var editor = page.Locator(@"textarea[role = ""textbox""]"); + await editor.IsVisibleAsync(); + await editor.FocusAsync(); + await editor.PressAsync("Control+a"); + await editor.PressAsync("Delete"); + } + + public static async Task> RequestRunAsync(this IPage page, MessageInterceptor interceptor, TimeSpan? delayStart = null) + { + await Task.Delay(delayStart ?? TimeSpan.FromSeconds(10)); + var awaiter = interceptor.AwaitForMessage("RunCompleted", TimeSpan.FromMinutes(10)); + await page.DispatchMessage(new + { + type = "run" + + }); + await awaiter; + return interceptor.Messages; + } + + public static async Task SetCodeUsingTryDotNetJsApi(this IPage page, MessageInterceptor interceptor, string code, TimeSpan? delayStart = null) + { + await page.RunAndWaitForConsoleMessageAsync(async () => + { + var dotnetOnline = new DotNetOnline(page); + var documentOpenAwaiter = interceptor.AwaitForMessage("DocumentOpened"); + await dotnetOnline.SetCodeAsync(code); + await documentOpenAwaiter; + }, + new PageRunAndWaitForConsoleMessageOptions + { + Predicate = message => message.Text.Contains($"[trydotnet-editor] [MonacoEditorArapter.setCode]: {code}"), + Timeout = Debugger.IsAttached ? 0.0f : (float)TimeSpan.FromMinutes(10).TotalMilliseconds + } + ); + } + + + public static async Task> ExecuteAssembly(this IPage page, string base64EncodedAssembly) + { + var messages = new List(); + var ts = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + await page.ExposeFunctionAsync("postMessageLogger", (JsonElement message) => + { + var wm = message.Deserialize()!; + messages.Add(wm); + + if (wm.type == "wasmRunner-result") + { + ts.SetResult(); + } + }); + + await page.DispatchMessage(new + { + type = "wasmRunner-command", + base64EncodedAssembly + }); + + await ts.Task.Timeout(TimeSpan.FromSeconds(60),"Timeout waiting for wasmRunner-result"); + + return messages; + + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PeakyTestDiscovery.cs b/src/Microsoft.TryDotNet.IntegrationTests/PeakyTestDiscovery.cs new file mode 100644 index 000000000..2c4e348c8 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/PeakyTestDiscovery.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Peaky.Client; +using Peaky.XUnit; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class PeakyTestDiscovery : PeakyXunitTestBase +{ + private static readonly Uri _testDiscoveryUri = new("https://mls-monitoring.azurewebsites.net/tests/staging/orchestrator?deployment=true"); + + private readonly PeakyClient _peakyClient = new(_testDiscoveryUri); + + public override PeakyClient PeakyClient => _peakyClient; +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PeakyTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/PeakyTests.cs new file mode 100644 index 000000000..0590e04e7 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/PeakyTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Peaky.Client; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class PeakyTests +{ + private readonly ITestOutputHelper _output; + + private readonly PeakyClient _peakyClient = new(new HttpClient()); + + public PeakyTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory(Skip = "Work in progress")] + [ClassData(typeof(PeakyTestDiscovery))] + public async Task The_peaky_test_passes(Uri url) + { + var result = await _peakyClient.GetTestResultAsync(url); + + _output.WriteLine(result.Content); + + result.Passed.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs new file mode 100644 index 000000000..9ee612230 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Playwright; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class PlaywrightSession : IDisposable +{ + private IPlaywright _playwright; + + public PlaywrightSession(IPlaywright playwright, IBrowser browser) + { + _playwright = playwright; + Browser = browser; + } + + public IBrowser Browser { get; } + + public static async Task StartAsync() + { + var exitCode = Playwright.Program.Main(["install", "chromium"]); + if (exitCode is not 0) + { + throw new Exception($"Playwright exited with code {exitCode}"); + } + + var session = await Playwright.Playwright.CreateAsync().Timeout(TimeSpan.FromMinutes(5), "Timeout creating Playwright session"); + + var browserTypeLaunchOptions = new BrowserTypeLaunchOptions(); + + if (Debugger.IsAttached) + { + browserTypeLaunchOptions.Headless = false; + browserTypeLaunchOptions.Devtools = true; + } + + var browser = await session.Chromium.LaunchAsync(browserTypeLaunchOptions).Timeout(TimeSpan.FromMinutes(5), "Timeout launching browser"); + + return new PlaywrightSession(session, browser); + } + + public void Dispose() => _playwright.Dispose(); +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightTestBase.cs b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightTestBase.cs new file mode 100644 index 000000000..0bddf5878 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightTestBase.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Pocket; +using Xunit; +using Xunit.Abstractions; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] + +namespace Microsoft.TryDotNet.IntegrationTests; + +[Collection(nameof(IntegratedServicesFixture))] +public abstract class PlaywrightTestBase : IDisposable +{ + private readonly CompositeDisposable _disposables = new(); + + protected PlaywrightTestBase( + IntegratedServicesFixture services, + ITestOutputHelper output) + { + Services = services; + Output = output; + } + + public IntegratedServicesFixture Services { get; } + + public ITestOutputHelper Output { get; } + + protected async Task NewPageAsync() + { + var playwright = await Services.GetPlaywrightAsync(); + return await playwright.Browser.NewPageAsync(); + } + + protected async Task TryDotNetUrlAsync() + { + var server = await Services.GetTryDotNetServerAsync(); + return server.Url; + } + + protected async Task LearnUrlAsync() + { + var server = await Services.GetLearnServerAsync(); + return server.Url; + } + + public void Dispose() + { + _disposables.Dispose(); + } +} + diff --git a/src/Microsoft.TryDotNet.IntegrationTests/SerializableCodeRunnerResult.cs b/src/Microsoft.TryDotNet.IntegrationTests/SerializableCodeRunnerResult.cs new file mode 100644 index 000000000..55434c0e5 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/SerializableCodeRunnerResult.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal record SerializableCodeRunnerResult(bool success, string? error, string? runnerError); \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs new file mode 100644 index 000000000..1567529e9 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Playwright; +using Pocket.For.Xunit; +using Xunit.Abstractions; + +namespace Microsoft.TryDotNet.IntegrationTests; + +[LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +public class TryDotNetJsIntegrationTests : PlaywrightTestBase +{ + public TryDotNetJsIntegrationTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output) + { + } + + [IntegrationTestFact(Skip = "Flaky in CI")] + public async Task loads_trydotnet_editor() + { + var page = await NewPageAsync(); + + var learnRoot = (await Services.GetLearnServerAsync()).Url; + var trydotnetOrigin = await TryDotNetUrlAsync(); + var trydotnetUrl = new Uri(trydotnetOrigin, "api/trydotnet.min.js"); + + var param = new Dictionary + { + ["trydotnetUrl"] = trydotnetUrl.ToString(), + ["trydotnetOrigin"] = trydotnetOrigin.ToString() + }; + + var pageUri = new Uri(QueryHelpers.AddQueryString(new Uri(learnRoot,"DocsHost.html").ToString(), param!)); + await page.GotoAsync(pageUri.ToString()); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + await page.FindEditor(); + } + + [IntegrationTestFact(Skip = "Flaky in CI")] + public async Task can_load_code() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + + var learnRoot = (await Services.GetLearnServerAsync()).Url; + var trydotnetOrigin = (await Services.GetTryDotNetServerAsync()).Url; + var trydotnetUrl = new Uri(trydotnetOrigin, "api/trydotnet.min.js"); + + var param = new Dictionary + { + ["trydotnetUrl"] = trydotnetUrl.ToString(), + ["trydotnetOrigin"] = trydotnetOrigin.ToString(), + }; + + var pageUri = new Uri(QueryHelpers.AddQueryString(new Uri(learnRoot, "DocsHost.html").ToString(), param!)); + await page.GotoAsync(pageUri.ToString()); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var dotnetOnline = new DotNetOnline(page); + + await dotnetOnline.FocusAsync(); + await page.SetCodeUsingTryDotNetJsApi(interceptor, "Console.WriteLine(123);"); + + await page.TestScreenShotAsync(); + var text = await page.FrameLocator("body > div > div.dotnet-online-editor-section > iframe").GetEditorContentAsync(); + text.Should().Contain("Console.WriteLine(123);"); + } + + [IntegrationTestFact(Skip = "Flaky in CI")] + public async Task outputs_are_rendered() + { + var page = await NewPageAsync(); + var interceptor = new MessageInterceptor(); + await interceptor.InstallAsync(page); + + var learnRoot = await LearnUrlAsync(); + var trydotnetOrigin = await TryDotNetUrlAsync(); + var trydotnetUrl = new Uri(trydotnetOrigin, "api/trydotnet.min.js"); + + var param = new Dictionary + { + ["trydotnetUrl"] = trydotnetUrl.ToString(), + ["trydotnetOrigin"] = trydotnetOrigin.ToString(), + }; + + var pageUri = new Uri(QueryHelpers.AddQueryString(new Uri(learnRoot, "DocsHost.html").ToString(), param!)); + await page.GotoAsync(pageUri.ToString()); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var dotnetOnline = new DotNetOnline(page); + + await dotnetOnline.FocusAsync(); + + await page.SetCodeUsingTryDotNetJsApi(interceptor, "Console.WriteLine(123);"); + + await page.TestScreenShotAsync("before_run"); + var run = interceptor.AwaitForMessage("RunCompleted", TimeSpan.FromMinutes(10)); + + await page.RunAndWaitForConsoleMessageAsync(async () => + { + await dotnetOnline.ExecuteAsync(); + }, new PageRunAndWaitForConsoleMessageOptions + { + Timeout = Debugger.IsAttached ? 0.0f: (float) TimeSpan.FromMinutes(10).TotalMilliseconds, + Predicate = message => message.Text.Contains("---- resolving response awaiter for") && message.Text.Contains("and type [RunCompleted]") + }); + + await run; + + await page.TestScreenShotAsync("after_run"); + var outputElement = page.Locator("body > div > div.dotnet-online-editor-section > pre"); + await page.TestScreenShotAsync("output_grabbed"); + var result = await outputElement.TextContentAsync(); + result.Should().Be("123\n"); + } +} diff --git a/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetServer.cs b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetServer.cs new file mode 100644 index 000000000..2edde9040 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetServer.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Pocket; +using static Pocket.Logger; + +namespace Microsoft.TryDotNet.IntegrationTests; + +public class TryDotNetServer : IDisposable +{ + private Process? _process; + + private TryDotNetServer(Process process, Uri url) + { + Url = url; + _process = process; + } + + public Uri Url { get; } + + public static async Task StartAsync() + { + using var operation = Log.OnEnterAndConfirmOnExit(); + + var uriFound = false; + var allOutput = new StringBuilder(); + var completionSource = new TaskCompletionSource(); + + var process = CommandLine.StartProcess( + "dotnet", + """ + Microsoft.TryDotNet.dll --urls="http://127.0.0.1:0" + """, + new DirectoryInfo(BuildProperties.TryDotNetPublishLocation), + output: output => + { + operation.Info(output); + allOutput.Append(output); + + if (!uriFound) + { + var matches = Regex.Match(output, @"listening on:\s*(?http(s)?://(\d+\.){3}(\d+)(:\d+)?)"); + if (matches.Success) + { + uriFound = true; + var result = new Uri(matches.Groups["URI"].Value, UriKind.Absolute); + completionSource.SetResult(result.ToLocalHost()); + } + } + }, + error: error => + { + var outputAndError = $""" + {allOutput} + + ERROR: + ------ + {error} + """; + + operation.Fail(message: error); + + completionSource.TrySetException(new Exception(outputAndError)); + }); + + operation.Info(("Process ID", process.Id), ("Process Name", process.ProcessName)); + + var timeout = Debugger.IsAttached ? TimeSpan.FromDays(1) : TimeSpan.FromMinutes(1); + + var url = await completionSource.Task.Timeout(timeout, $"ASP.NET process not ready within {timeout.TotalSeconds}s. Output =\n{allOutput})"); + + operation.Succeed(); + + return new TryDotNetServer(process, url); + } + + public void Dispose() + { + if (_process is { } process) + { + using var operation = Log.OnEnterAndConfirmOnExit(arg: ("Process ID", process.Id)); + _process = null; + process.Kill(true); + process.Dispose(); + operation.Succeed(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/UriExtensions.cs b/src/Microsoft.TryDotNet.IntegrationTests/UriExtensions.cs new file mode 100644 index 000000000..8e185ba1c --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/UriExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal static class UriExtensions +{ + public static Uri ToLocalHost(this Uri source) + { + var root = new Uri($"{source.Scheme}://127.0.0.1:{source.Port}"); + return new Uri(root,source.PathAndQuery); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerMessage.cs b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerMessage.cs new file mode 100644 index 000000000..3b40693f1 --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerMessage.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace Microsoft.TryDotNet.IntegrationTests; + +internal record WasmRunnerMessage(string type, string? message = null, SerializableCodeRunnerResult? result = null) { } \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs new file mode 100644 index 000000000..9354b9efc --- /dev/null +++ b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +using FluentAssertions; + +using Microsoft.Playwright; +using Pocket.For.Xunit; +using Xunit.Abstractions; + +namespace Microsoft.TryDotNet.IntegrationTests; + +[LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +public class WasmRunnerTests : PlaywrightTestBase +{ + public WasmRunnerTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output) + { + + } + + [IntegrationTestFact] + public async Task can_load_wasmrunner() + { + var page = await NewPageAsync(); + await page.GotoAsync(await TryDotNetUrlAsync() + "wasmrunner"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator(@"id=wasmRunner-sentinel").IsHiddenAsync(); + + await page.TestScreenShotAsync(); + } + + [IntegrationTestFact] + public async Task can_run_assembly() + { + var page = await NewPageAsync(); + await page.GotoAsync(await TryDotNetUrlAsync() + "wasmrunner"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator(@"id=wasmRunner-sentinel").IsHiddenAsync(); + + var messages = await page.ExecuteAssembly( + @"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAECAG+WAqcAAAAAAAAAAOAAIiALATAAAAYAAAACAAAAAAAAYiUAAAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAABAlAABPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAwAAAD0JAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAaAUAAAAgAAAABgAAAAIAAAAAAAAAAAAAAAAAACAAAGAucmVsb2MAAAwAAAAAQAAAAAIAAAAIAAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEJQAAAAAAAEgAAAACAAUAcCAAAIQEAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAcgEAAHAoCgAACgAqIgIoCwAACgAqIgIoCwAACgAqQlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAAhAEAACN+AADwAQAA2AEAACNTdHJpbmdzAAAAAMgDAAAcAAAAI1VTAOQDAAAQAAAAI0dVSUQAAAD0AwAAkAAAACNCbG9iAAAAAAAAAAIAAAFHFAAACQAAAAD6ATMAFgAAAQAAAAwAAAADAAAAAwAAAAsAAAAJAAAAAQAAAAEAAAAAAEoBAQAAAAAABgDaAKIBBgAsAaIBBgBNAI8BDwDCAQAABgATAXEBBgC7AHEBBgB4AHEBBgCVAHEBBgD6AHEBBgBhAHEBBgDRAWUBBgAdAGUBAAAAAAgAAAAAAAEAAQAAABAAXQGDAS0AAQABAAEAEAABADQALQABAAMAUCAAAAAAkQBsASMAAQBeIAAAAACGGIkBBgABAGcgAAAAAIYYiQEGAAEACQCJAQEAEQCJAQYAGQCJAQoAKQCJARAAMQCJARAAOQCJARAAQQCJARAASQCJARAAUQCJARAAYQBDABUAWQCJAQYALgALACcALgATADAALgAbAE8ALgAjAFgALgArAGwALgAzAHcALgA7AIQALgBDAFgALgBLAFgABIAAAAEAAAAAAAAAAAAAAAAAJQAAAAIAAAAAAAAAAAAAABoAEQAAAAAAAAAAAABDbGFzczEAPE1vZHVsZT4AbmV0c3RhbmRhcmQAQ29uc29sZQBibGF6b3ItY29uc29sZQBibGF6b3JfY29uc29sZQBXcml0ZUxpbmUARGVidWdnYWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAGJsYXpvci1jb25zb2xlLmRsbABQcm9ncmFtAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAG15QXBwAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAERlYnVnZ2luZ01vZGVzAE9iamVjdAAAGUgAZQBsAGwAbwAgAFcAbwByAGwAZAAhAAAAXQ9GanmtTEuzgc0NDhYR8wAEIAEBCAMgAAEFIAEBEREEIAEBDgQAAQEOCMx7E//NLd1RAwAAAQgBAAgAAAAAAB4BAAEAVAIWV3JhcE5vbkV4Y2VwdGlvblRocm93cwEIAQAHAQAAAAATAQAOYmxhem9yLWNvbnNvbGUAAAoBAAVEZWJ1ZwAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4JQAAAAAAAAAAAABSJQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARCUAAAAAAAAAAAAAAABfQ29yRGxsTWFpbgBtc2NvcmVlLmRsbAAAAAAA/yUAIAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAGQ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + ); + + await page.TestScreenShotAsync(); + + messages.Should() + .ContainSingle(m => m.type == "wasmRunner-result") + .Which + .result!.success.Should().Be(true); + } + + [IntegrationTestFact] + public async Task can_run_assembly_2() + { + var page = await NewPageAsync(); + await page.GotoAsync(await TryDotNetUrlAsync() + "wasmrunner"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator(@"id=wasmRunner-sentinel").IsHiddenAsync(); + + var messages = await page.ExecuteAssembly( + @"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAECAESGl2IAAAAAAAAAAOAAIgALATAAAAYAAAACAAAAAAAAfiUAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAACwlAABPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAhAUAAAAgAAAABgAAAAIAAAAAAAAAAAAAAAAAACAAAGAucmVsb2MAAAwAAAAAQAAAAAIAAAAIAAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgJQAAAAAAAEgAAAACAAUAZCAAAMgEAAABAAAAAQAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAH3soCwAACgAqIgIoDAAACgAqQlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAAmAEAACN+AAAEAgAA8AEAACNTdHJpbmdzAAAAAPQDAAAEAAAAI1VTAPgDAAAQAAAAI0dVSUQAAAAIBAAAwAAAACNCbG9iAAAAAAAAAAIAAAFHFQAACQAAAAD6ATMAFgAAAQAAAA0AAAACAAAAAgAAAAEAAAAMAAAACgAAAAEAAAACAAAAAABQAQEAAAAAAAYA4AC1AQYAMgG1AQYAOgCiAQ8A1QEAAAYAZQBcAQYAGQGKAQYAwQCKAQYAfgCKAQYAmwCKAQYAAAGKAQYATgCKAQYA6QF+AQoAEQB+AQAAAAABAAAAAAABAAEAAAAQAHYBdgExAAEAAQBQIAAAAACRAIUBIwABAFsgAAAAAIYYnAEGAAIAAAABAOQBCQCcAQEAEQCcAQYAGQCcAQoAKQCcARAAMQCcARAAOQCcARAAQQCcARAASQCcARAAUQCcARAAWQCcARAAaQAwABUAYQCcAQYALgALACkALgATADIALgAbAFEALgAjAFoALgArAJAALgAzAJ0ALgA7AKgALgBDALUALgBLAJAALgBTAJAABIAAAAEAAAAAAAAAAAAAAAAAGQAAAAYAAAAAAAAAAAAAABoAIQAAAAAABgAAAAAAAAAAAAAAGgAKAAAAAAAAAAAAADxNb2R1bGU+AFN5c3RlbS5Db25zb2xlAGNvbnNvbGUAU3lzdGVtLlJ1bnRpbWUAV3JpdGVMaW5lAERlYnVnZ2FibGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0dHJpYnV0ZQBUYXJnZXRGcmFtZXdvcmtBdHRyaWJ1dGUAQXNzZW1ibHlGaWxlVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUluZm9ybWF0aW9uYWxWZXJzaW9uQXR0cmlidXRlAEFzc2VtYmx5Q29uZmlndXJhdGlvbkF0dHJpYnV0ZQBDb21waWxhdGlvblJlbGF4YXRpb25zQXR0cmlidXRlAEFzc2VtYmx5UHJvZHVjdEF0dHJpYnV0ZQBBc3NlbWJseUNvbXBhbnlBdHRyaWJ1dGUAUnVudGltZUNvbXBhdGliaWxpdHlBdHRyaWJ1dGUAY29uc29sZS5leGUAU3lzdGVtLlJ1bnRpbWUuVmVyc2lvbmluZwBQcm9ncmFtAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAERlYnVnZ2luZ01vZGVzAGFyZ3MAT2JqZWN0AAAAAADDjLfZIkfuSa5Es1ckswwxAAQgAQEIAyAAAQUgAQEREQQgAQEOBAABAQgIsD9ffxHVCjoFAAEBHQ4IAQAIAAAAAAAeAQABAFQCFldyYXBOb25FeGNlcHRpb25UaHJvd3MBCAEABwEAAAAANQEAGC5ORVRDb3JlQXBwLFZlcnNpb249djYuMAEAVA4URnJhbWV3b3JrRGlzcGxheU5hbWUADAEAB2NvbnNvbGUAAAoBAAVEZWJ1ZwAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAVCUAAAAAAAAAAAAAbiUAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAlAAAAAAAAAAAAAAAAX0NvckV4ZU1haW4AbXNjb3JlZS5kbGwAAAAAAP8lACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAIA1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + ); + + await page.TestScreenShotAsync(); + + messages.Should() + .ContainSingle(m => m.type == "wasmRunner-result") + .Which + .result!.success.Should().Be(true); + } + + [IntegrationTestFact] + public async Task can_run_assembly_and_produce_output() + { + var page = await NewPageAsync(); + await page.GotoAsync(await TryDotNetUrlAsync() + "wasmrunner"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await page.Locator(@"id=wasmRunner-sentinel").IsHiddenAsync(); + + var messages = await page.ExecuteAssembly( + @"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAECAG+WAqcAAAAAAAAAAOAAIiALATAAAAYAAAACAAAAAAAAYiUAAAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAABAlAABPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAwAAAD0JAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAaAUAAAAgAAAABgAAAAIAAAAAAAAAAAAAAAAAACAAAGAucmVsb2MAAAwAAAAAQAAAAAIAAAAIAAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEJQAAAAAAAEgAAAACAAUAcCAAAIQEAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAcgEAAHAoCgAACgAqIgIoCwAACgAqIgIoCwAACgAqQlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAAhAEAACN+AADwAQAA2AEAACNTdHJpbmdzAAAAAMgDAAAcAAAAI1VTAOQDAAAQAAAAI0dVSUQAAAD0AwAAkAAAACNCbG9iAAAAAAAAAAIAAAFHFAAACQAAAAD6ATMAFgAAAQAAAAwAAAADAAAAAwAAAAsAAAAJAAAAAQAAAAEAAAAAAEoBAQAAAAAABgDaAKIBBgAsAaIBBgBNAI8BDwDCAQAABgATAXEBBgC7AHEBBgB4AHEBBgCVAHEBBgD6AHEBBgBhAHEBBgDRAWUBBgAdAGUBAAAAAAgAAAAAAAEAAQAAABAAXQGDAS0AAQABAAEAEAABADQALQABAAMAUCAAAAAAkQBsASMAAQBeIAAAAACGGIkBBgABAGcgAAAAAIYYiQEGAAEACQCJAQEAEQCJAQYAGQCJAQoAKQCJARAAMQCJARAAOQCJARAAQQCJARAASQCJARAAUQCJARAAYQBDABUAWQCJAQYALgALACcALgATADAALgAbAE8ALgAjAFgALgArAGwALgAzAHcALgA7AIQALgBDAFgALgBLAFgABIAAAAEAAAAAAAAAAAAAAAAAJQAAAAIAAAAAAAAAAAAAABoAEQAAAAAAAAAAAABDbGFzczEAPE1vZHVsZT4AbmV0c3RhbmRhcmQAQ29uc29sZQBibGF6b3ItY29uc29sZQBibGF6b3JfY29uc29sZQBXcml0ZUxpbmUARGVidWdnYWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAGJsYXpvci1jb25zb2xlLmRsbABQcm9ncmFtAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAG15QXBwAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAERlYnVnZ2luZ01vZGVzAE9iamVjdAAAGUgAZQBsAGwAbwAgAFcAbwByAGwAZAAhAAAAXQ9GanmtTEuzgc0NDhYR8wAEIAEBCAMgAAEFIAEBEREEIAEBDgQAAQEOCMx7E//NLd1RAwAAAQgBAAgAAAAAAB4BAAEAVAIWV3JhcE5vbkV4Y2VwdGlvblRocm93cwEIAQAHAQAAAAATAQAOYmxhem9yLWNvbnNvbGUAAAoBAAVEZWJ1ZwAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4JQAAAAAAAAAAAABSJQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARCUAAAAAAAAAAAAAAABfQ29yRGxsTWFpbgBtc2NvcmVlLmRsbAAAAAAA/yUAIAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAGQ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + ); + + await page.TestScreenShotAsync(); + + messages.Should() + .ContainSingle(m => m.type == "wasmRunner-stdout") + .Which + .message.Should().Be("Hello World!\n"); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/ApiContractScenario.cs b/src/Microsoft.TryDotNet.SimulatorGenerator/ApiContractScenario.cs new file mode 100644 index 000000000..844b4ec33 --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/ApiContractScenario.cs @@ -0,0 +1,5 @@ +using Microsoft.DotNet.Interactive.Commands; + +namespace Microsoft.TryDotNet.SimulatorGenerator; + +internal record ApiContractScenario(string Label, KernelCommand[][] CommandBatches); \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/ApiEndpointSimulatorGenerator.cs b/src/Microsoft.TryDotNet.SimulatorGenerator/ApiEndpointSimulatorGenerator.cs new file mode 100644 index 000000000..564e47c2d --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/ApiEndpointSimulatorGenerator.cs @@ -0,0 +1,611 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Net.Http.Json; +using System.Text.Encodings.Web; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Connection; +using Microsoft.DotNet.Interactive.CSharpProject; +using Microsoft.DotNet.Interactive.CSharpProject.Commands; +using Microsoft.TryDotNet.Tests; + +namespace Microsoft.TryDotNet.SimulatorGenerator; + +public class ApiEndpointSimulatorGenerator +{ + static ApiEndpointSimulatorGenerator() + { + CSharpProjectKernel.RegisterEventsAndCommands(); + } + + public static async Task CreateScenarioFiles(DirectoryInfo destinationFolder) + { + foreach (var apiContractScenario in ApiContractScenarios()) + { + var simulatorConfiguration = await ExecuteScenario(apiContractScenario); + + var filename = Path.Combine(destinationFolder.FullName, apiContractScenario.Label + ".json"); + Console.WriteLine($"Creating configuration for scenario {apiContractScenario.Label} at '{filename}'"); + await File.WriteAllTextAsync(filename,simulatorConfiguration); + } + } + + private static IEnumerable ApiContractScenarios() + { + { + yield return new ApiContractScenario( + "open_project", + new[] + { + new[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("./Program.cs", + @" +public class Program +{ + public static void Main(string[] args) + { + #region REGION_1 + var a = 123; + #endregion + + #region REGION_2 + var b = 123; + #endregion + } +}") + })) + } + } + ); + + yield return new ApiContractScenario( + "open_document", + new[] + { + new[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("./Program.cs", + @" +public class Program +{ + public static void Main(string[] args) + { + #region REGION_1 + var a = 123; + #endregion + + #region REGION_2 + var b = 123; + #endregion + } +}") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("./Program.cs", + @" +public class Program +{ + public static void Main(string[] args) + { + #region REGION_1 + var a = 123; + #endregion + + #region REGION_2 + var b = 123; + #endregion + } +}") + })), + new OpenDocument("./Program.cs") + } + } + ); + + yield return new ApiContractScenario( + "open_document_with_region", + new[] + { + new[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("./Program.cs", + @" +public class Program +{ + public static void Main(string[] args) + { + #region REGION_1 + var a = 123; + #endregion + + #region REGION_2 + var b = 123; + #endregion + } +}") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("./Program.cs", + @" +public class Program +{ + public static void Main(string[] args) + { + #region REGION_1 + var a = 123; + #endregion + + #region REGION_2 + var b = 123; + #endregion + } +}") + })), + new OpenDocument("./Program.cs", "REGION_2") + } + } + ); + + yield return new ApiContractScenario( + "compiles_with_no_warning", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + #region test-region + #endregion + } +} +") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region") + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region"), + new SubmitCode("System.Console.WriteLine(2);"), + new CompileProject() + } + } + ); + + yield return new ApiContractScenario( + "compiles_with_error", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region") + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region"), + new SubmitCode("someInt = \"NaN\";"), + new CompileProject() + } + } + ); + + var markedCode = @"fileInfo.$$"; + MarkupTestFile.GetLineAndColumn(markedCode, out var code, out var line, out var character); + yield return new ApiContractScenario( + "completions_produced_from_regions", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + var fileInfo = new System.IO.FileInfo(""test.file""); + #region TEST_REGION + #endregion + } +} +") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + var fileInfo = new System.IO.FileInfo(""test.file""); + #region TEST_REGION + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "TEST_REGION") + }, + + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + var fileInfo = new System.IO.FileInfo(""test.file""); + #region TEST_REGION + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "TEST_REGION"), + new RequestCompletions(code, new LinePosition(line, character)) + } + } + + ); + + yield return new ApiContractScenario( + "diagnostics_produced_with_errors_in_code", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region") + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region"), + new RequestDiagnostics("someInt = \"NaN\";") + } + } + ); + + yield return new ApiContractScenario( + "diagnostics_produced_with_hidden_severity", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region") + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("Program.cs", @" +using System.Linq; +using System; + +public class Program +{ + public static void Main(string[] args) + { + int someInt = 1; + #region test-region + #endregion + } +} +") + })), + new OpenDocument("Program.cs", regionName: "test-region"), + new RequestDiagnostics("someInt = 4;") + } + } + ); + + yield return new ApiContractScenario( + "update_project", + new[] + { + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("program.cs", @" +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; +namespace Program { + class Program { + static void Main(string[] args){ + #region controller + #endregion + } + } +}")})) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("program.cs", @" +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; +namespace Program { + class Program { + static void Main(string[] args){ + #region controller + #endregion + } + } +}")})), + new OpenDocument("program.cs", "controller") + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("program.cs", @" +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; +namespace Program { + class Program { + static void Main(string[] args){ + #region controller + Console.WriteLine(123); + #endregion + } + } +}")})) + }, + new KernelCommand[] + { + new OpenProject(new Project(new[] + { + new ProjectFile("program.cs", @" +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; +namespace Program { + class Program { + static void Main(string[] args){ + #region controller + Console.WriteLine(123); + #endregion + } + } +}")})), + new OpenDocument("program.cs", "controller") + } + + }); + } + + } + + + private static async Task ExecuteScenario(ApiContractScenario scenario) + { + await using var applicationBuilderFactory = new WebApplicationFactory(); + + var c = applicationBuilderFactory.CreateDefaultClient(); + var transcript = new + { + requests = new List() + }; + + foreach (var commandBatch in scenario.CommandBatches) + { + var request = new + { + commands = commandBatch.Select(KernelCommandEnvelope.Create).Select(e => e.ToJsonElement()) + }; + + var requestBody = JsonContent.Create(request); + + var response = await c.PostAsync("commands", requestBody); + + var responseJson = JsonDocument.Parse(await response.Content.ReadAsStringAsync(CancellationToken.None)).RootElement; + + transcript.requests.Add(new + { + commands = request.commands, + events = responseJson.GetProperty("events") + }); + } + + var options = new JsonSerializerOptions(JsonSerializerDefaults.General) + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + return JsonSerializer.Serialize(transcript, options).Fixed(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/MarkupTestFile.cs b/src/Microsoft.TryDotNet.SimulatorGenerator/MarkupTestFile.cs new file mode 100644 index 000000000..280682bce --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/MarkupTestFile.cs @@ -0,0 +1,313 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.TryDotNet.Tests +{ + /// + /// To aid with testing, we define a special type of text file that can encode additional + /// information in it. This prevents a test writer from having to carry around multiple sources + /// of information that must be reconstituted. For example, instead of having to keep around the + /// contents of a file *and* and the location of the cursor, the tester can just provide a + /// string with the "$" character in it. This allows for easy creation of "FIT" tests where all + /// that needs to be provided are strings that encode every bit of state necessary in the string + /// itself. + /// + /// The current set of encoded features we support are: + /// + /// $$ - The position in the file. There can be at most one of these. + /// + /// [| ... |] - A span of text in the file. There can be many of these and they can be nested + /// and/or overlap the $ position. + /// + /// {|Name: ... |} A span of text in the file annotated with an identifier. There can be many of + /// these, including ones with the same name. + /// + /// Additional encoded features can be added on a case by case basis. + /// + public static class MarkupTestFile + { + private const string PositionString = "$$"; + private const string SpanStartString = "[|"; + private const string SpanEndString = "|]"; + private const string NamedSpanStartString = "{|"; + private const string NamedSpanEndString = "|}"; + + private static readonly Regex s_namedSpanStartRegex = new Regex(@"\{\| ([-_.A-Za-z0-9\+]+) \:", + RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); + + private static void Parse( + string input, + out string output, + out int? position, + out Dictionary> spans) + { + position = null; + var tempSpans = new Dictionary>(); + + var outputBuilder = new StringBuilder(); + + var currentIndexInInput = 0; + var inputOutputOffset = 0; + + // A stack of span starts along with their associated annotation name. [||] spans simply + // have empty string for their annotation name. + var spanStartStack = new Stack>(); + + while (true) + { + var matches = new List>(); + AddMatch(input, PositionString, currentIndexInInput, matches); + AddMatch(input, SpanStartString, currentIndexInInput, matches); + AddMatch(input, SpanEndString, currentIndexInInput, matches); + AddMatch(input, NamedSpanEndString, currentIndexInInput, matches); + + var namedSpanStartMatch = s_namedSpanStartRegex.Match(input, currentIndexInInput); + if (namedSpanStartMatch.Success) + { + matches.Add(Tuple.Create(namedSpanStartMatch.Index, namedSpanStartMatch.Value)); + } + + if (matches.Count == 0) + { + // No more markup to process. + break; + } + + var orderedMatches = matches.OrderBy(t => t, MatchComparer.Instance).ToList(); + if (orderedMatches.Count >= 2 && + spanStartStack.Count > 0 && + matches[0].Item1 == matches[1].Item1 - 1) + { + // We have a slight ambiguity with cases like these: + // + // [|] [|} + // + // Is it starting a new match, or ending an existing match. As a workaround, we + // special case these and consider it ending a match if we have something on the + // stack already. + if ((matches[0].Item2 == SpanStartString && matches[1].Item2 == SpanEndString && spanStartStack.Peek().Item2 == string.Empty) || + (matches[0].Item2 == SpanStartString && matches[1].Item2 == NamedSpanEndString && spanStartStack.Peek().Item2 != string.Empty)) + { + orderedMatches.RemoveAt(0); + } + } + + // Order the matches by their index + var firstMatch = orderedMatches.First(); + + var matchIndexInInput = firstMatch.Item1; + var matchString = firstMatch.Item2; + + var matchIndexInOutput = matchIndexInInput - inputOutputOffset; + outputBuilder.Append(input.Substring(currentIndexInInput, matchIndexInInput - currentIndexInInput)); + + currentIndexInInput = matchIndexInInput + matchString.Length; + inputOutputOffset += matchString.Length; + + switch (matchString.Substring(0, 2)) + { + case PositionString: + if (position.HasValue) + { + throw new ArgumentException($"Saw multiple occurrences of {PositionString}"); + } + + position = matchIndexInOutput; + break; + + case SpanStartString: + spanStartStack.Push(Tuple.Create(matchIndexInOutput, string.Empty)); + break; + + case SpanEndString: + if (spanStartStack.Count == 0) + { + throw new ArgumentException($"Saw {SpanEndString} without matching {SpanStartString}"); + } + + if (spanStartStack.Peek().Item2.Length > 0) + { + throw new ArgumentException( + $"Saw {NamedSpanStartString} without matching {NamedSpanEndString}"); + } + + PopSpan(spanStartStack, tempSpans, matchIndexInOutput); + break; + + case NamedSpanStartString: + var name = namedSpanStartMatch.Groups[1].Value; + spanStartStack.Push(Tuple.Create(matchIndexInOutput, name)); + break; + + case NamedSpanEndString: + if (spanStartStack.Count == 0) + { + throw new ArgumentException( + $"Saw {NamedSpanEndString} without matching {NamedSpanStartString}"); + } + + if (spanStartStack.Peek().Item2.Length == 0) + { + throw new ArgumentException($"Saw {SpanStartString} without matching {SpanEndString}"); + } + + PopSpan(spanStartStack, tempSpans, matchIndexInOutput); + break; + + default: + throw new InvalidOperationException(); + } + } + + if (spanStartStack.Count > 0) + { + throw new ArgumentException($"Saw {SpanStartString} without matching {SpanEndString}"); + } + + // Append the remainder of the string. + outputBuilder.Append(input.Substring(currentIndexInInput)); + output = outputBuilder.ToString(); + spans = tempSpans.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private static V GetOrAdd(IDictionary dictionary, K key, Func function) + { + if (!dictionary.TryGetValue(key, out var value)) + { + value = function(key); + dictionary.Add(key, value); + } + + return value; + } + + private static void PopSpan( + Stack> spanStartStack, + IDictionary> spans, + int finalIndex) + { + var spanStartTuple = spanStartStack.Pop(); + + var span = TextSpan.FromBounds(spanStartTuple.Item1, finalIndex); + GetOrAdd(spans, spanStartTuple.Item2, _ => + { + var list = new List(); + return list; + }).Add(span); + } + + private static void AddMatch(string input, string value, int currentIndex, List> matches) + { + var index = input.IndexOf(value, currentIndex, StringComparison.Ordinal); + if (index >= 0) + { + matches.Add(Tuple.Create(index, value)); + } + } + + private static void GetPositionAndSpans( + string input, + out string output, + out int? cursorPositionOpt, + out ImmutableArray spans) + { + Parse(input, out output, out cursorPositionOpt, out var dictionary); + + var builder = GetOrAdd(dictionary, string.Empty, _ => new List()); + spans = builder.ToImmutableArray(); + } + + public static void GetPositionAndNamedSpans( + string input, + out string output, + out int? cursorPositionOpt, + out IDictionary> spans) + { + Parse(input, out output, out cursorPositionOpt, out var dictionary); + spans = dictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToImmutableArray()); + } + + public static void GetNamedSpans(string input, out string output, out IDictionary> spans) + => GetPositionAndNamedSpans(input, out output, out var cursorPositionOpt, out spans); + + public static void GetPosition(string input, out string output, out int? cursorPosition) + => GetPositionAndSpans(input, out output, out cursorPosition, out ImmutableArray spans); + + public static void GetLineAndColumn(string input, out string output, out int line, out int column) + { + GetPosition(input, out output, out var position); + line = 0; + column = 0; + for (int i = 0; i < position; i++) + { + switch (output[i]) + { + case '\n': + line++; + column = 0; + break; + default: + column++; + break; + } + } + } + + public static void GetPositionAndSpan(string input, out string output, out int? cursorPosition, out TextSpan? textSpan) + { + GetPositionAndSpans(input, out output, out cursorPosition, out ImmutableArray spans); + textSpan = spans.Length == 0 ? null : (TextSpan?)spans.Single(); + } + + public static void GetPositionAndSpan(string input, out string output, out int? cursorPosition, out TextSpan textSpan) + { + GetPositionAndSpans(input, out output, out cursorPosition, out var spans); + textSpan = spans.Single(); + } + + public static void GetSpans(string input, out string output, out ImmutableArray spans) + { + GetPositionAndSpans(input, out output, out int? pos, out spans); + } + + public static void GetSpan(string input, out string output, out TextSpan textSpan) + { + GetSpans(input, out output, out ImmutableArray spans); + textSpan = spans.Single(); + } + + private class MatchComparer : IComparer> + { + public static readonly IComparer> Instance = new MatchComparer(); + + private MatchComparer() + { + } + + public int Compare(Tuple? x, Tuple? y) + { + if (x is null && y is null) + { + return 0; + } + else if (x is null) + { + return -1; + } + else if (y is null) + { + return 1; + } + else + { + return x.Item1 - y.Item1; + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/Microsoft.TryDotNet.SimulatorGenerator.csproj b/src/Microsoft.TryDotNet.SimulatorGenerator/Microsoft.TryDotNet.SimulatorGenerator.csproj new file mode 100644 index 000000000..9a1817f23 --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/Microsoft.TryDotNet.SimulatorGenerator.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + embedded + + + + + + + + + + + + + diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/Program.cs b/src/Microsoft.TryDotNet.SimulatorGenerator/Program.cs new file mode 100644 index 000000000..17361080f --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/Program.cs @@ -0,0 +1,21 @@ +using System.CommandLine; +using Microsoft.TryDotNet.SimulatorGenerator; + +var existingOnlyOption = new Option("--destination-folder") +{ + Description = "Location to write the simulator files", + IsRequired = true +}.ExistingOnly(); + +var command = new RootCommand +{ + existingOnlyOption +}; + +command.SetHandler(async (DirectoryInfo destinationFolder) => +{ + + await ApiEndpointSimulatorGenerator.CreateScenarioFiles(destinationFolder); +}, existingOnlyOption); + +return command.Invoke(args); diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/Properties/launchSettings.json b/src/Microsoft.TryDotNet.SimulatorGenerator/Properties/launchSettings.json new file mode 100644 index 000000000..8eec446ad --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Microsoft.TryDotNet.SimulatorGenerator": { + "commandName": "Project", + "commandLineArgs": "--destination-folder \"$(MSBuildThisFileDirectory)../microsoft-trydotnet-editor/tests/simulatorConfigurations/apiService\"" + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/StringExtensions.cs b/src/Microsoft.TryDotNet.SimulatorGenerator/StringExtensions.cs new file mode 100644 index 000000000..39efca9d3 --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/StringExtensions.cs @@ -0,0 +1,32 @@ +using System.Text.RegularExpressions; + +namespace Microsoft.TryDotNet.SimulatorGenerator; + +internal static class StringExtensions +{ + public static string FixedToken(this string source) + { + return Regex.Replace(source, @"""token""\s*:\s*""(?([^""\\]|(\\.))*)""", @"""token"": ""command-token""", RegexOptions.IgnoreCase); + } + + public static string FixedId(this string source) + { + return Regex.Replace(source, @"""id""\s*:\s*""(?([^""\\]|(\\.))*)""", @"""id"": ""command-id""", RegexOptions.IgnoreCase); + } + + public static string FixedNewLine(this string source) + { + return Regex.Replace(source, @"\\r\\n", @"\n"); + } + + public static string FixedAssembly(this string source) + { + return Regex.Replace(source, @"(?""assembly""\s*:\s*\{\s*""value""\s*:\s*"")(?([^""\\]|(\\.))*)(?""\s*\}\s*)", "${start}AABBCC${end}", RegexOptions.Multiline); + } + + + public static string Fixed(this string source) + { + return source.FixedId().FixedToken().FixedNewLine().FixedAssembly(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.SimulatorGenerator/es-metadata.yml b/src/Microsoft.TryDotNet.SimulatorGenerator/es-metadata.yml new file mode 100644 index 000000000..e2300d960 --- /dev/null +++ b/src/Microsoft.TryDotNet.SimulatorGenerator/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: ab4d58de-beb3-4714-866c-1b46a48ea002 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\NET Tools\TryDotNET\trydotnet \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.Tests/(Pocket)/Logger/LogEvents.SubscribeToPocketLogger.cs b/src/Microsoft.TryDotNet.Tests/(Pocket)/Logger/LogEvents.SubscribeToPocketLogger.cs new file mode 100644 index 000000000..95f513606 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/(Pocket)/Logger/LogEvents.SubscribeToPocketLogger.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Interactive.CSharpProject; +using Microsoft.DotNet.Interactive.Events; +using Xunit.Abstractions; + +namespace Pocket; + +internal partial class LogEvents +{ + public static IDisposable SubscribeToPocketLogger(this ITestOutputHelper output) => + Subscribe( + e => output.WriteLine(e.ToLogString()), + new[] + { + typeof(LogEvents).Assembly, + typeof(KernelEvent).Assembly, + typeof(CSharpProjectKernel).Assembly + }); +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.Tests/CodeRunnerTests.cs b/src/Microsoft.TryDotNet.Tests/CodeRunnerTests.cs new file mode 100644 index 000000000..4b40cb9b4 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/CodeRunnerTests.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.TryDotNet.WasmRunner; +using Xunit; + +namespace Microsoft.TryDotNet.Tests; + +public class CodeRunnerTests +{ + [Fact] + public async Task It_can_run_code_and_return_result() + { + var runner = new CodeRunner(); + var encodedAssembly = @"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mwAeAUABmwAeAWABmwAeAUIAaQBvAAMAAQAAAAYEmAAAAEMEnAACAAcAAwACAAkABQADAAoAGQADAAwAGwADAA4AHQADABAAHwADABIAIQADABQAIwADABYAJQAfAC4ABIAAAAEAAAAAAAAAAAAAAAAAzAAAAAQAAgABAAAAAAAAAHQA1AAAAAAABAABAAEAAAAAAAAAdABmAgAAAAAEAAIAAQAAAAAAAAB0ANECAAAAAAQAAQABAAAAAAAAAHQAvQAAAAAABAACAAEAAAAAAAAAdAChAwAAAAADAAIAKQBcAAAAADxjdXJyZW50PjVfXzEAPEZpYm9uYWNjaT5kX18xAElFbnVtZXJhYmxlYDEASUVudW1lcmF0b3JgMQBJbnQzMgA8bmV4dD41X18yADxNb2R1bGU+AFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljAGdldF9DdXJyZW50TWFuYWdlZFRocmVhZElkADw+bF9faW5pdGlhbFRocmVhZElkAFRha2UASUVudW1lcmFibGUASURpc3Bvc2FibGUAU3lzdGVtLkNvbnNvbGUAY29uc29sZQBTeXN0ZW0uUnVudGltZQBXcml0ZUxpbmUAVHlwZQBTeXN0ZW0uSURpc3Bvc2FibGUuRGlzcG9zZQA8PjFfX3N0YXRlAENvbXBpbGVyR2VuZXJhdGVkQXR0cmlidXRlAERlYnVnZ2FibGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0dHJpYnV0ZQBJdGVyYXRvclN0YXRlTWFjaGluZUF0dHJpYnV0ZQBEZWJ1Z2dlckhpZGRlbkF0dHJpYnV0ZQBBc3NlbWJseUZpbGVWZXJzaW9uQXR0cmlidXRlAEFzc2VtYmx5SW5mb3JtYXRpb25hbFZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAENvbXBpbGF0aW9uUmVsYXhhdGlvbnNBdHRyaWJ1dGUAQXNzZW1ibHlQcm9kdWN0QXR0cmlidXRlAEFzc2VtYmx5Q29tcGFueUF0dHJpYnV0ZQBSdW50aW1lQ29tcGF0aWJpbGl0eUF0dHJpYnV0ZQBTeXN0ZW0uRGlhZ25vc3RpY3MuRGVidWcARmlib25hY2NpAGNvbnNvbGUuZGxsAFByb2dyYW0AU3lzdGVtAE1haW4AU3lzdGVtLlJlZmxlY3Rpb24ATm90U3VwcG9ydGVkRXhjZXB0aW9uAFN5c3RlbS5MaW5xAElFbnVtZXJhdG9yAFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLklFbnVtZXJhYmxlPFN5c3RlbS5JbnQzMj4uR2V0RW51bWVyYXRvcgBTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmFibGUuR2V0RW51bWVyYXRvcgAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBTeXN0ZW0uUnVudGltZS5FeHRlbnNpb25zAFN5c3RlbS5Db2xsZWN0aW9ucwBPYmplY3QAU3lzdGVtLkNvbGxlY3Rpb25zLklFbnVtZXJhdG9yLlJlc2V0AEVudmlyb25tZW50AFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLklFbnVtZXJhdG9yPFN5c3RlbS5JbnQzMj4uQ3VycmVudABTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmF0b3IuQ3VycmVudABTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5JRW51bWVyYXRvcjxTeXN0ZW0uSW50MzI+LmdldF9DdXJyZW50AFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYXRvci5nZXRfQ3VycmVudAA8PjJfX2N1cnJlbnQATW92ZU5leHQAAAAAAPTh3kBkgaRGgtnPq+FiClEABCABAQgDIAABBSABARERBCABAQ4FIAEBEjUDIAACBRUSMQEIBCAAEwADIAAcBRUSPQEICCAAFRIxARMABCAAEkkIBwIVEjEBCAgQEAECFRI9AR4AFRI9AR4ACAMKAQgEAAEBCAMAAAgFBwMICAIEBwESDAiwP19/EdUKOgIGCAMAAAEHAAAVEj0BCAMgAAgHIAAVEjEBCAMoAAgDKAAcCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAcBAAAAAAwBAAdjb25zb2xlAAAKAQAFRGVidWcAAAwBAAcxLjAuMC4wAAAKAQAFMS4wLjAAABwBABdQcm9ncmFtKzxGaWJvbmFjY2k+ZF9fMQAABAEAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAFQsAAAAAAAAAAAAAG4sAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgLAAAAAAAAAAAAAAAAF9Db3JFeGVNYWluAG1zY29yZWUuZGxsAAAAAAD/JQAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAMAAAAgDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + var outputs = new List(); + var errors = new List(); + var result = await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + + result.Succeeded.Should().BeTrue(); + } + + [Fact] + public async Task It_produces_outputs() + { + var runner = new CodeRunner(); + var encodedAssembly = @"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mwAeAUABmwAeAWABmwAeAUIAaQBvAAMAAQAAAAYEmAAAAEMEnAACAAcAAwACAAkABQADAAoAGQADAAwAGwADAA4AHQADABAAHwADABIAIQADABQAIwADABYAJQAfAC4ABIAAAAEAAAAAAAAAAAAAAAAAzAAAAAQAAgABAAAAAAAAAHQA1AAAAAAABAABAAEAAAAAAAAAdABmAgAAAAAEAAIAAQAAAAAAAAB0ANECAAAAAAQAAQABAAAAAAAAAHQAvQAAAAAABAACAAEAAAAAAAAAdAChAwAAAAADAAIAKQBcAAAAADxjdXJyZW50PjVfXzEAPEZpYm9uYWNjaT5kX18xAElFbnVtZXJhYmxlYDEASUVudW1lcmF0b3JgMQBJbnQzMgA8bmV4dD41X18yADxNb2R1bGU+AFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljAGdldF9DdXJyZW50TWFuYWdlZFRocmVhZElkADw+bF9faW5pdGlhbFRocmVhZElkAFRha2UASUVudW1lcmFibGUASURpc3Bvc2FibGUAU3lzdGVtLkNvbnNvbGUAY29uc29sZQBTeXN0ZW0uUnVudGltZQBXcml0ZUxpbmUAVHlwZQBTeXN0ZW0uSURpc3Bvc2FibGUuRGlzcG9zZQA8PjFfX3N0YXRlAENvbXBpbGVyR2VuZXJhdGVkQXR0cmlidXRlAERlYnVnZ2FibGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0dHJpYnV0ZQBJdGVyYXRvclN0YXRlTWFjaGluZUF0dHJpYnV0ZQBEZWJ1Z2dlckhpZGRlbkF0dHJpYnV0ZQBBc3NlbWJseUZpbGVWZXJzaW9uQXR0cmlidXRlAEFzc2VtYmx5SW5mb3JtYXRpb25hbFZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAENvbXBpbGF0aW9uUmVsYXhhdGlvbnNBdHRyaWJ1dGUAQXNzZW1ibHlQcm9kdWN0QXR0cmlidXRlAEFzc2VtYmx5Q29tcGFueUF0dHJpYnV0ZQBSdW50aW1lQ29tcGF0aWJpbGl0eUF0dHJpYnV0ZQBTeXN0ZW0uRGlhZ25vc3RpY3MuRGVidWcARmlib25hY2NpAGNvbnNvbGUuZGxsAFByb2dyYW0AU3lzdGVtAE1haW4AU3lzdGVtLlJlZmxlY3Rpb24ATm90U3VwcG9ydGVkRXhjZXB0aW9uAFN5c3RlbS5MaW5xAElFbnVtZXJhdG9yAFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLklFbnVtZXJhYmxlPFN5c3RlbS5JbnQzMj4uR2V0RW51bWVyYXRvcgBTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmFibGUuR2V0RW51bWVyYXRvcgAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBTeXN0ZW0uUnVudGltZS5FeHRlbnNpb25zAFN5c3RlbS5Db2xsZWN0aW9ucwBPYmplY3QAU3lzdGVtLkNvbGxlY3Rpb25zLklFbnVtZXJhdG9yLlJlc2V0AEVudmlyb25tZW50AFN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLklFbnVtZXJhdG9yPFN5c3RlbS5JbnQzMj4uQ3VycmVudABTeXN0ZW0uQ29sbGVjdGlvbnMuSUVudW1lcmF0b3IuQ3VycmVudABTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5JRW51bWVyYXRvcjxTeXN0ZW0uSW50MzI+LmdldF9DdXJyZW50AFN5c3RlbS5Db2xsZWN0aW9ucy5JRW51bWVyYXRvci5nZXRfQ3VycmVudAA8PjJfX2N1cnJlbnQATW92ZU5leHQAAAAAAPTh3kBkgaRGgtnPq+FiClEABCABAQgDIAABBSABARERBCABAQ4FIAEBEjUDIAACBRUSMQEIBCAAEwADIAAcBRUSPQEICCAAFRIxARMABCAAEkkIBwIVEjEBCAgQEAECFRI9AR4AFRI9AR4ACAMKAQgEAAEBCAMAAAgFBwMICAIEBwESDAiwP19/EdUKOgIGCAMAAAEHAAAVEj0BCAMgAAgHIAAVEjEBCAMoAAgDKAAcCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAcBAAAAAAwBAAdjb25zb2xlAAAKAQAFRGVidWcAAAwBAAcxLjAuMC4wAAAKAQAFMS4wLjAAABwBABdQcm9ncmFtKzxGaWJvbmFjY2k+ZF9fMQAABAEAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAFQsAAAAAAAAAAAAAG4sAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgLAAAAAAAAAAAAAAAAF9Db3JFeGVNYWluAG1zY29yZWUuZGxsAAAAAAD/JQAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAMAAAAgDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + var outputs = new List(); + var errors = new List(); + await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + + outputs.Select(o => o.Trim()).Should().BeEquivalentTo( + "1", "1", "2", "3", "5", "8", "13", "21", + "34", "55", "89", "144", "233", "377", "610", + "987", "1597", "2584", "4181", "6765"); + + } + + [Fact] + public async Task It_can_run_a_main_method_with_parameters() + { + var runner = new CodeRunner(); + var encodedAssembly = @"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAECAESGl2IAAAAAAAAAAOAAIgALATAAAAYAAAACAAAAAAAAfiUAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAACwlAABPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAhAUAAAAgAAAABgAAAAIAAAAAAAAAAAAAAAAAACAAAGAucmVsb2MAAAwAAAAAQAAAAAIAAAAIAAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgJQAAAAAAAEgAAAACAAUAZCAAAMgEAAABAAAAAQAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAH3soCwAACgAqIgIoDAAACgAqQlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAAmAEAACN+AAAEAgAA8AEAACNTdHJpbmdzAAAAAPQDAAAEAAAAI1VTAPgDAAAQAAAAI0dVSUQAAAAIBAAAwAAAACNCbG9iAAAAAAAAAAIAAAFHFQAACQAAAAD6ATMAFgAAAQAAAA0AAAACAAAAAgAAAAEAAAAMAAAACgAAAAEAAAACAAAAAABQAQEAAAAAAAYA4AC1AQYAMgG1AQYAOgCiAQ8A1QEAAAYAZQBcAQYAGQGKAQYAwQCKAQYAfgCKAQYAmwCKAQYAAAGKAQYATgCKAQYA6QF+AQoAEQB+AQAAAAABAAAAAAABAAEAAAAQAHYBdgExAAEAAQBQIAAAAACRAIUBIwABAFsgAAAAAIYYnAEGAAIAAAABAOQBCQCcAQEAEQCcAQYAGQCcAQoAKQCcARAAMQCcARAAOQCcARAAQQCcARAASQCcARAAUQCcARAAWQCcARAAaQAwABUAYQCcAQYALgALACkALgATADIALgAbAFEALgAjAFoALgArAJAALgAzAJ0ALgA7AKgALgBDALUALgBLAJAALgBTAJAABIAAAAEAAAAAAAAAAAAAAAAAGQAAAAYAAAAAAAAAAAAAABoAIQAAAAAABgAAAAAAAAAAAAAAGgAKAAAAAAAAAAAAADxNb2R1bGU+AFN5c3RlbS5Db25zb2xlAGNvbnNvbGUAU3lzdGVtLlJ1bnRpbWUAV3JpdGVMaW5lAERlYnVnZ2FibGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0dHJpYnV0ZQBUYXJnZXRGcmFtZXdvcmtBdHRyaWJ1dGUAQXNzZW1ibHlGaWxlVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUluZm9ybWF0aW9uYWxWZXJzaW9uQXR0cmlidXRlAEFzc2VtYmx5Q29uZmlndXJhdGlvbkF0dHJpYnV0ZQBDb21waWxhdGlvblJlbGF4YXRpb25zQXR0cmlidXRlAEFzc2VtYmx5UHJvZHVjdEF0dHJpYnV0ZQBBc3NlbWJseUNvbXBhbnlBdHRyaWJ1dGUAUnVudGltZUNvbXBhdGliaWxpdHlBdHRyaWJ1dGUAY29uc29sZS5leGUAU3lzdGVtLlJ1bnRpbWUuVmVyc2lvbmluZwBQcm9ncmFtAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAERlYnVnZ2luZ01vZGVzAGFyZ3MAT2JqZWN0AAAAAADDjLfZIkfuSa5Es1ckswwxAAQgAQEIAyAAAQUgAQEREQQgAQEOBAABAQgIsD9ffxHVCjoFAAEBHQ4IAQAIAAAAAAAeAQABAFQCFldyYXBOb25FeGNlcHRpb25UaHJvd3MBCAEABwEAAAAANQEAGC5ORVRDb3JlQXBwLFZlcnNpb249djYuMAEAVA4URnJhbWV3b3JrRGlzcGxheU5hbWUADAEAB2NvbnNvbGUAAAoBAAVEZWJ1ZwAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAVCUAAAAAAAAAAAAAbiUAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAlAAAAAAAAAAAAAAAAX0NvckV4ZU1haW4AbXNjb3JlZS5kbGwAAAAAAP8lACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAIA1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; + var outputs = new List(); + var errors = new List(); + await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + + outputs.Select(o => o.Trim()).Should().BeEquivalentTo( + "123"); + } + + + [Fact] + public async Task It_can_run_a_private_main_method() + { + /* Compiled from +using System; +namespace myApp +{ +class Program +{ + static void Main() + { + Console.WriteLine("Hello World!"); + } +} +} +*/ + var runner = new CodeRunner(); + var encodedAssembly = @"TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAECAG+WAqcAAAAAAAAAAOAAIiALATAAAAYAAAACAAAAAAAAYiUAAAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAABgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAABAlAABPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAwAAAD0JAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAaAUAAAAgAAAABgAAAAIAAAAAAAAAAAAAAAAAACAAAGAucmVsb2MAAAwAAAAAQAAAAAIAAAAIAAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEJQAAAAAAAEgAAAACAAUAcCAAAIQEAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYAcgEAAHAoCgAACgAqIgIoCwAACgAqIgIoCwAACgAqQlNKQgEAAQAAAAAADAAAAHY0LjAuMzAzMTkAAAAABQBsAAAAhAEAACN+AADwAQAA2AEAACNTdHJpbmdzAAAAAMgDAAAcAAAAI1VTAOQDAAAQAAAAI0dVSUQAAAD0AwAAkAAAACNCbG9iAAAAAAAAAAIAAAFHFAAACQAAAAD6ATMAFgAAAQAAAAwAAAADAAAAAwAAAAsAAAAJAAAAAQAAAAEAAAAAAEoBAQAAAAAABgDaAKIBBgAsAaIBBgBNAI8BDwDCAQAABgATAXEBBgC7AHEBBgB4AHEBBgCVAHEBBgD6AHEBBgBhAHEBBgDRAWUBBgAdAGUBAAAAAAgAAAAAAAEAAQAAABAAXQGDAS0AAQABAAEAEAABADQALQABAAMAUCAAAAAAkQBsASMAAQBeIAAAAACGGIkBBgABAGcgAAAAAIYYiQEGAAEACQCJAQEAEQCJAQYAGQCJAQoAKQCJARAAMQCJARAAOQCJARAAQQCJARAASQCJARAAUQCJARAAYQBDABUAWQCJAQYALgALACcALgATADAALgAbAE8ALgAjAFgALgArAGwALgAzAHcALgA7AIQALgBDAFgALgBLAFgABIAAAAEAAAAAAAAAAAAAAAAAJQAAAAIAAAAAAAAAAAAAABoAEQAAAAAAAAAAAABDbGFzczEAPE1vZHVsZT4AbmV0c3RhbmRhcmQAQ29uc29sZQBibGF6b3ItY29uc29sZQBibGF6b3JfY29uc29sZQBXcml0ZUxpbmUARGVidWdnYWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAGJsYXpvci1jb25zb2xlLmRsbABQcm9ncmFtAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAG15QXBwAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAERlYnVnZ2luZ01vZGVzAE9iamVjdAAAGUgAZQBsAGwAbwAgAFcAbwByAGwAZAAhAAAAXQ9GanmtTEuzgc0NDhYR8wAEIAEBCAMgAAEFIAEBEREEIAEBDgQAAQEOCMx7E//NLd1RAwAAAQgBAAgAAAAAAB4BAAEAVAIWV3JhcE5vbkV4Y2VwdGlvblRocm93cwEIAQAHAQAAAAATAQAOYmxhem9yLWNvbnNvbGUAAAoBAAVEZWJ1ZwAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA4JQAAAAAAAAAAAABSJQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARCUAAAAAAAAAAAAAAABfQ29yRGxsTWFpbgBtc2NvcmVlLmRsbAAAAAAA/yUAIAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAGQ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; + var outputs = new List(); + var errors = new List(); + var result = await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + result.Succeeded.Should().BeTrue(); + outputs.Select(o => o.Trim()).Should().BeEquivalentTo("Hello World!"); + } + + [Fact] + public async Task It_provides_errors_for_missing_type_at_runtime() + { + var runner = new CodeRunner(); + var outputs = new List(); + var errors = new List(); + var referencedCompilation = Compile(@" +public class C +{ +}"); + + var text = @" +class D +{ + public static void Main() + { + C c= new C(); + } +}"; + var other = Compile(text, referencedCompilation.ToMetadataReference()); + + + await using var stream = new MemoryStream(); + other.Emit(peStream: stream); + var encodedAssembly = Convert.ToBase64String(stream.ToArray()); + + + var result = await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + result.Succeeded.Should().BeFalse(); + result.RunnerException!.Message.Should().Match("*Could not load type 'C'*"); + } + + + [Fact] + public async Task It_returns_an_error_if_there_is_no_entry_point() + { + var runner = new CodeRunner(); + var outputs = new List(); + var errors = new List(); + var compilation = Compile("class C {}"); + + await using var stream = new MemoryStream(); + compilation.Emit(peStream: stream); + var encodedAssembly = Convert.ToBase64String(stream.ToArray()); + + var result = await runner.RunAssemblyEntryPoint(encodedAssembly, outputs.Add, errors.Add); + result.Succeeded.Should().BeFalse(); + result.RunnerException!.Message.Should().Be( + "Could not find a static entry point 'Main'."); + } + + + private static Compilation Compile(string text, params MetadataReference[] additionalReferences) + { + var refs = new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(decimal).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Console).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location), + }.Concat(additionalReferences); + + return CSharpCompilation.Create("assembly.dll", new[] { CSharpSyntaxTree.ParseText(text) }, refs, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.Tests/CommandExecutionTests.cs b/src/Microsoft.TryDotNet.Tests/CommandExecutionTests.cs new file mode 100644 index 000000000..b91dcda49 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/CommandExecutionTests.cs @@ -0,0 +1,144 @@ +using System.Net.Http.Json; +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.DotNet.Interactive.Connection; +using Microsoft.DotNet.Interactive.CSharpProject.Events; +using Pocket; +using Pocket.For.Xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.TryDotNet.Tests; + +[LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +public class CommandExecutionTests : IDisposable +{ + private readonly CompositeDisposable _disposables = new(); + + public CommandExecutionTests(ITestOutputHelper output) + { + _disposables.Add(output.SubscribeToPocketLogger()); + } + + [Fact] + public async Task can_compile_projects_with_user_code_in_region() + { + await using var applicationBuilderFactory = new WebApplicationFactory(); + + var c = applicationBuilderFactory.CreateDefaultClient(); + + var requests = """ + { + "commands": [ + { + "commandType": "OpenProject", + "command": { + "project": { + "files": [ + { + "relativeFilePath": "program.cs", + "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\n\nnamespace Program\n{\n class Program\n {\n static void Main(string[] args)\n {\n #region controller\n\n #endregion\n }\n }\n}" + } + ] + } + }, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22||6" + }, + { + "commandType": "OpenDocument", + "command": { + "relativeFilePath": "./program.cs", + "regionName": "controller" + }, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22||7" + }, + { + "commandType": "SubmitCode", + "command": { + "code": "var a = 123;" + }, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22||8" + }, + { + "commandType": "CompileProject", + "command": {}, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22" + } + ] + } + """; + + var requestBody = JsonContent.Create(JsonDocument.Parse(requests).RootElement); + + var response = await c.PostAsync("commands", requestBody); + + var responseJson = JsonDocument.Parse(await response.Content.ReadAsStringAsync(CancellationToken.None)).RootElement; + + var events = responseJson.GetProperty("events").EnumerateArray().Select(KernelEventEnvelope.Deserialize).Select(ee => ee.Event).ToList(); + + using var _ = new AssertionScope(); + + response.EnsureSuccessStatusCode(); + + var assemblyProduced = events.OfType().SingleOrDefault(); + assemblyProduced.Should().NotBeNull(); + assemblyProduced!.Assembly.Value.Should().NotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task can_open_document_with_user_code_in_region() + { + await using var applicationBuilderFactory = new WebApplicationFactory(); + + var c = applicationBuilderFactory.CreateDefaultClient(); + + var requests = """ + { + "commands": [ + { + "commandType": "OpenProject", + "command": { + "project": { + "files": [ + { + "relativeFilePath": "program.cs", + "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\n\nnamespace Program\n{\n class Program\n {\n static void Main(string[] args)\n {\n #region controller\nConsole.WriteLine(123);\n #endregion\n }\n }\n}" + } + ] + } + }, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22||6" + }, + { + "commandType": "OpenDocument", + "command": { + "relativeFilePath": "program.cs", + "regionName": "controller" + }, + "token": "595d327c-b14f-5ad7-7da0-2579cbfa9961::22||7" + } + ] + } + """; + + var requestBody = JsonContent.Create(JsonDocument.Parse(requests).RootElement); + + var response = await c.PostAsync("commands", requestBody); + + var responseJson = JsonDocument.Parse(await response.Content.ReadAsStringAsync(CancellationToken.None)).RootElement; + + var events = responseJson.GetProperty("events").EnumerateArray().Select(KernelEventEnvelope.Deserialize).Select(ee => ee.Event).ToList(); + + using var _ = new AssertionScope(); + + response.EnsureSuccessStatusCode(); + + var documentOpened = events.OfType().SingleOrDefault(); + documentOpened.Should().NotBeNull(); + documentOpened!.Content.Should().Contain("Console.WriteLine(123);"); + } + + public void Dispose() => _disposables.Dispose(); +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.csproj b/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.csproj new file mode 100644 index 000000000..9fcf05f74 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.csproj @@ -0,0 +1,54 @@ + + + + net9.0 + enable + enable + all + embedded + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + + + all + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.v3.ncrunchproject b/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.v3.ncrunchproject new file mode 100644 index 000000000..4739b8225 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.Tests/es-metadata.yml b/src/Microsoft.TryDotNet.Tests/es-metadata.yml new file mode 100644 index 000000000..e2300d960 --- /dev/null +++ b/src/Microsoft.TryDotNet.Tests/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: ab4d58de-beb3-4714-866c-1b46a48ea002 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\NET Tools\TryDotNET\trydotnet \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/App.razor b/src/Microsoft.TryDotNet.WasmRunner/App.razor new file mode 100644 index 000000000..9ee3d8386 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/App.razor @@ -0,0 +1,14 @@ +@using Microsoft.TryDotNet.WasmRunner +@using Microsoft.TryDotNet.WasmRunner.Shared + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/src/Microsoft.TryDotNet.WasmRunner/AsyncContext.cs b/src/Microsoft.TryDotNet.WasmRunner/AsyncContext.cs new file mode 100644 index 000000000..299e72918 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/AsyncContext.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace Microsoft.TryDotNet.WasmRunner; + +internal static class AsyncContext +{ + private static int _seed = 0; + + private static readonly AsyncLocal _id = new(); + + public static int? Id => _id.Value; + + public static bool TryEstablish(out int id) + { + if (_id.Value is { } value) + { + id = _id.Value.Value; + return false; + } + else + { + _id.Value = Interlocked.Increment(ref _seed); + id = _id.Value.Value; + return true; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/CodeRunner.cs b/src/Microsoft.TryDotNet.WasmRunner/CodeRunner.cs new file mode 100644 index 000000000..c2cb04a56 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/CodeRunner.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reactive.Disposables; +using System.Reflection; + +namespace Microsoft.TryDotNet.WasmRunner; + +public class CodeRunner +{ + public Task RunAssemblyEntryPoint(string base64EncodedAssembly, Action onOutput, Action onError) + { + var assembly = LoadFromBase64EncodedString(base64EncodedAssembly); + return RunAssemblyEntryPoint(assembly, onOutput, onError); + } + + private static Assembly LoadFromBase64EncodedString(string base64EncodedAssembly) + { + if (string.IsNullOrWhiteSpace(base64EncodedAssembly)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(base64EncodedAssembly)); + } + var bytes = Convert.FromBase64String(base64EncodedAssembly); + return Assembly.Load(bytes); + + } + + public async Task RunAssemblyEntryPoint(Assembly assembly, Action onOutput, + Action onError) + { + await Task.Yield(); + + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + MethodInfo entryPoint; + var success = false; + Exception? lastError = null; + try + { + entryPoint = EntryPointDiscoverer.FindStaticEntryMethod(assembly); + } + catch (InvalidProgramException ie) + { + return new RunResults(success, runnerException: ie); + } + + using (var _ = ConsoleOutput.Subscribe(c => + { + return new CompositeDisposable + { + c.Out.Subscribe( onOutput), + c.Error.Subscribe(error => + { + lastError = new Exception(error); + onError(error); + }) + }; + })) + { + try + { + var parameters = entryPoint.GetParameters(); + if (parameters.Length != 0) + { + var parameterValues = parameters.Select(p => + p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null).ToArray(); + entryPoint.Invoke(null, parameterValues); + } + else + { + entryPoint.Invoke(null, null); + } + success = true; + } + catch (Exception e) + { + switch (e.InnerException) + { + case TypeLoadException: + return new RunResults(success, exception: e, runnerException: e.InnerException); + default: + return new RunResults(success, exception: e); + } + + } + } + + success = success && (lastError is null); + + return new RunResults(success,exception:lastError); + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/CodeRunnerAdapter.cs b/src/Microsoft.TryDotNet.WasmRunner/CodeRunnerAdapter.cs new file mode 100644 index 000000000..eba686ebb --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/CodeRunnerAdapter.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.JSInterop; + +namespace Microsoft.TryDotNet.WasmRunner; + +internal class CodeRunnerAdapter +{ + private readonly CodeRunner _runner; + private readonly IJSRuntime _jsInterop; + + public CodeRunnerAdapter(CodeRunner runner, IJSRuntime jsInterop) + { + _runner = runner; + _jsInterop = jsInterop; + } + + [JSInvokable] + public async Task RunAssembly(string base64EncodedAssembly) + { + var result = await _runner.RunAssemblyEntryPoint(base64EncodedAssembly, + output => _jsInterop.InvokeAsync("publishCodeRunnerStdOut", output), + error => _jsInterop.InvokeAsync("publishCodeRunnerStdError", error) + ); + + return new SerializableCodeRunnerResult(result.Succeeded, result.Exception?.ToString(), result.RunnerException?.ToString()); + } +} + +internal record SerializableCodeRunnerResult(bool Success, string? Error, string? RunnerError); \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/ConsoleOutput.cs b/src/Microsoft.TryDotNet.WasmRunner/ConsoleOutput.cs new file mode 100644 index 000000000..de0fcae39 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/ConsoleOutput.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +using System.Reactive.Disposables; + +namespace Microsoft.TryDotNet.WasmRunner; + +#nullable disable + +internal static class ConsoleOutput +{ + private static readonly object _systemConsoleSwapLock = new(); + + private static MultiplexingTextWriter _multiplexingOutputWriter; + private static MultiplexingTextWriter _multiplexingErrorWriter; + private static TextWriter _originalOutputWriter; + private static TextWriter _originalErrorWriter; + + private static int _refCount = 0; + + public static IDisposable Subscribe(Func subscribe) + { + + lock (_systemConsoleSwapLock) + { + + if (++_refCount == 1) + { + _originalOutputWriter = Console.Out; + _originalErrorWriter = Console.Error; + _multiplexingOutputWriter = new MultiplexingTextWriter("out"); + _multiplexingErrorWriter = new MultiplexingTextWriter("err"); + Console.SetOut(_multiplexingOutputWriter); + Console.SetError(_multiplexingErrorWriter); + } + } + + var outWriterForContext = _multiplexingOutputWriter.EnsureInitializedForCurrentAsyncContext(); + var errWriterForContext = _multiplexingErrorWriter.EnsureInitializedForCurrentAsyncContext(); + + var obsConsole = new ObservableConsole( + _multiplexingOutputWriter.GetObservable(), + _multiplexingErrorWriter.GetObservable()); + + return new CompositeDisposable( + subscribe(obsConsole), + outWriterForContext, + errWriterForContext, + Disposable.Create(() => + { + lock (_systemConsoleSwapLock) + { + if (--_refCount == 0) + { + Console.SetOut(_originalOutputWriter); + Console.SetError(_originalErrorWriter); + _multiplexingOutputWriter.Dispose(); + _multiplexingOutputWriter = null; + _multiplexingErrorWriter.Dispose(); + _multiplexingErrorWriter = null; + } + } + })); + } + + public record ObservableConsole( + IObservable Out, + IObservable Error); +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/EntryPointDiscoverer.cs b/src/Microsoft.TryDotNet.WasmRunner/EntryPointDiscoverer.cs new file mode 100644 index 000000000..8617b0fc2 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/EntryPointDiscoverer.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.TryDotNet.WasmRunner; + +internal class EntryPointDiscoverer +{ + public static MethodInfo FindStaticEntryMethod(Assembly assembly, string? entryPointFullTypeName = null) + { + var candidates = new List(); + + if (!string.IsNullOrWhiteSpace(entryPointFullTypeName)) + { + var typeInfo = assembly.GetType(entryPointFullTypeName, false, false)?.GetTypeInfo(); + if (typeInfo == null) + { + throw new InvalidProgramException($"Could not find '{entryPointFullTypeName}' specified for Main method. See project property."); + } + FindMainMethodCandidates(typeInfo, candidates); + } + else + { + foreach (var type in assembly + .DefinedTypes + .Where(t => t.IsClass) + .Where(t => t.GetCustomAttribute() is null)) + { + FindMainMethodCandidates(type, candidates); + } + } + + string MainMethodFullName() + { + return string.IsNullOrWhiteSpace(entryPointFullTypeName) ? "Main" : $"{entryPointFullTypeName}.Main"; + } + + if (candidates.Count > 1) + { + throw new AmbiguousMatchException( + $"Ambiguous entry point. Found multiple static functions named '{MainMethodFullName()}'. Could not identify which method is the main entry point for this function."); + } + + if (candidates.Count == 0) + { + throw new InvalidProgramException( + $"Could not find a static entry point '{MainMethodFullName()}'."); + } + + return candidates[0]; + } + + private static void FindMainMethodCandidates(TypeInfo type, List candidates) + { + foreach (var method in type + .GetMethods(BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic) + .Where(m => + string.Equals("Main", m.Name, StringComparison.OrdinalIgnoreCase))) + { + if (method.ReturnType == typeof(void) + || method.ReturnType == typeof(int) + || method.ReturnType == typeof(Task) + || method.ReturnType == typeof(Task)) + { + candidates.Add(method); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/Microsoft.TryDotNet.WasmRunner.csproj b/src/Microsoft.TryDotNet.WasmRunner/Microsoft.TryDotNet.WasmRunner.csproj new file mode 100644 index 000000000..d675be24e --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Microsoft.TryDotNet.WasmRunner.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + wasmrunner + false + embedded + + + + + + + + + + diff --git a/src/Microsoft.TryDotNet.WasmRunner/MultiplexingTextWriter.cs b/src/Microsoft.TryDotNet.WasmRunner/MultiplexingTextWriter.cs new file mode 100644 index 000000000..8e1cf0f22 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/MultiplexingTextWriter.cs @@ -0,0 +1,319 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Text; + +namespace Microsoft.TryDotNet.WasmRunner; + +#nullable disable + +internal class MultiplexingTextWriter : TextWriter +{ + private static volatile UnicodeEncoding _encoding; + + private readonly Func _createTextWriter; + private readonly string _name; + private readonly ConcurrentDictionary _writers = new(); + + private readonly TextWriter _defaultWriter; + + public MultiplexingTextWriter( + string name, + Func createTextWriter = null, + TextWriter defaultWriter = null) + { + _createTextWriter = createTextWriter ?? DefaultCreateTextWriter; + _name = name; + _defaultWriter = defaultWriter ?? Null; + } + + private TextWriter DefaultCreateTextWriter() + { + return new ObservableStringWriter(); + } + + public IDisposable EnsureInitializedForCurrentAsyncContext() + { + AsyncContext.TryEstablish(out var id); + + return Disposable.Create(() => + { + var success = _writers.TryGetValue(id, out var writer); + + if (success) + { + writer.Dispose(); + _writers.TryRemove(id, out _); + } + }); + } + + private TextWriter GetCurrentWriter() + { + + if (AsyncContext.Id is { } asyncContextId) + { + var writer = _writers.GetOrAdd( + asyncContextId, + _ => _createTextWriter()); + + return writer; + } + + return _defaultWriter; + } + + public IObservable GetObservable() + { + if (GetCurrentWriter() is IObservable observable) + { + return observable; + } + + return Observable.Empty(); + } + + public override Encoding Encoding => _encoding ?? (_encoding = new UnicodeEncoding(false, false)); + + public IEnumerable Writers => _writers.Select(w => w.Value); + + public override void Write(char value) + { + GetCurrentWriter().Write(value); + } + + public override void WriteLine() + { + GetCurrentWriter().WriteLine(); + } + + public override void WriteLine(char value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void Write(char[] buffer, int index, int count) + { + GetCurrentWriter().Write(buffer, index, count); + } + + public override void Write(string value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(bool value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(char[] buffer) + { + GetCurrentWriter().Write(buffer); + } + + public override void Write(decimal value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(double value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(int value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(long value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(object value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(float value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(string format, object arg0) + { + GetCurrentWriter().Write(format, arg0); + } + + public override void Write(string format, object arg0, object arg1) + { + GetCurrentWriter().Write(format, arg0, arg1); + } + + public override void Write(string format, object arg0, object arg1, object arg2) + { + GetCurrentWriter().Write(format, arg0, arg1, arg2); + } + + public override void Write(string format, params object[] arg) + { + GetCurrentWriter().Write(format, arg); + } + + public override void Write(uint value) + { + GetCurrentWriter().Write(value); + } + + public override void Write(ulong value) + { + GetCurrentWriter().Write(value); + } + + public override Task WriteAsync(char value) + { + return GetCurrentWriter().WriteAsync(value); + } + + public override Task WriteAsync(char[] buffer, int index, int count) + { + return GetCurrentWriter().WriteAsync(buffer, index, count); + } + + public override Task WriteAsync(string value) + { + return GetCurrentWriter().WriteAsync(value); + } + + public override void WriteLine(bool value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(char[] buffer) + { + GetCurrentWriter().WriteLine(buffer); + } + + public override void WriteLine(char[] buffer, int index, int count) + { + GetCurrentWriter().WriteLine(buffer, index, count); + } + + public override void WriteLine(decimal value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(double value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(int value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(long value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(object value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(float value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(string value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(string format, object arg0) + { + GetCurrentWriter().WriteLine(format, arg0); + } + + public override void WriteLine(string format, object arg0, object arg1) + { + GetCurrentWriter().WriteLine(format, arg0, arg1); + } + + public override void WriteLine(string format, object arg0, object arg1, object arg2) + { + GetCurrentWriter().WriteLine(format, arg0, arg1, arg2); + } + + public override void WriteLine(string format, params object[] arg) + { + GetCurrentWriter().WriteLine(format, arg); + } + + public override void WriteLine(uint value) + { + GetCurrentWriter().WriteLine(value); + } + + public override void WriteLine(ulong value) + { + GetCurrentWriter().WriteLine(value); + } + + public override Task WriteLineAsync() + { + return GetCurrentWriter().WriteLineAsync(); + } + + public override Task WriteLineAsync(char value) + { + return GetCurrentWriter().WriteLineAsync(value); + } + + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + return GetCurrentWriter().WriteLineAsync(buffer, index, count); + } + + public override Task WriteLineAsync(string value) + { + return GetCurrentWriter().WriteLineAsync(value); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + var writers = _writers.Values.ToArray(); + _writers.Clear(); + foreach (var writer in writers) + { + writer.Dispose(); + } + } + + base.Dispose(disposing); + } + + public override string ToString() + { + if (AsyncContext.Id is { } asyncContextId && + _writers.TryGetValue(asyncContextId, out var writer)) + { + + return writer.ToString(); + } + + return ""; + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/ObservableStringWriter.cs b/src/Microsoft.TryDotNet.WasmRunner/ObservableStringWriter.cs new file mode 100644 index 000000000..959a0787e --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/ObservableStringWriter.cs @@ -0,0 +1,343 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using System.Text; + +namespace Microsoft.TryDotNet.WasmRunner; + +#nullable disable + +internal class ObservableStringWriter : StringWriter, IObservable +{ + private readonly Subject _writeEvents = new(); + + private readonly List _regions = new(); + + private bool _trackingWriteOperation; + + private int _observerCount; + + private readonly CompositeDisposable _disposable; + + + public ObservableStringWriter() + { + + _disposable = new CompositeDisposable + { + _writeEvents + }; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _disposable.Dispose(); + } + + base.Dispose(disposing); + } + + public override void Write(char value) + { + TrackWriteOperation(() => base.Write(value)); + } + + private void PublishStringIfObserved(StringBuilder sb, TextSpan textSpan) + { + if (_observerCount > 0) + { + _writeEvents.OnNext(sb.ToString(textSpan.Start, textSpan.Length)); + } + } + + private void TrackWriteOperation(Action action) + { + if (_trackingWriteOperation) + { + action(); + return; + } + + _trackingWriteOperation = true; + var sb = base.GetStringBuilder(); + + var region = new TextSpan + { + Start = sb.Length + }; + + _regions.Add(region); + + action(); + + region.Length = sb.Length - region.Start; + _trackingWriteOperation = false; + PublishStringIfObserved(sb, region); + } + + private async Task TrackWriteOperationAsync(Func action) + { + if (_trackingWriteOperation) + { + await action(); + return; + } + + _trackingWriteOperation = true; + var sb = base.GetStringBuilder(); + + var region = new TextSpan + { + Start = sb.Length + }; + + _regions.Add(region); + + await action(); + + region.Length = sb.Length - region.Start; + + _trackingWriteOperation = false; + + PublishStringIfObserved(sb, region); + } + + public override void Write(char[] buffer, int index, int count) + { + TrackWriteOperation(() => base.Write(buffer, index, count)); + } + + public override void Write(string value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override Task WriteAsync(char value) + { + return TrackWriteOperationAsync(() => base.WriteAsync(value)); + } + + public override Task WriteAsync(char[] buffer, int index, int count) + { + return TrackWriteOperationAsync(() => base.WriteAsync(buffer, index, count)); + } + + public override Task WriteAsync(string value) + { + return TrackWriteOperationAsync(() => base.WriteAsync(value)); + } + + public override Task WriteLineAsync(char value) + { + return TrackWriteOperationAsync(() => base.WriteLineAsync(value)); + } + + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + return TrackWriteOperationAsync(() => base.WriteLineAsync(buffer, index, count)); + } + + public override Task WriteLineAsync(string value) + { + return TrackWriteOperationAsync(() => base.WriteLineAsync(value)); + } + + public override void Write(bool value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(char[] buffer) + { + TrackWriteOperation(() => base.Write(buffer)); + } + + public override void Write(decimal value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(double value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(int value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(long value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(object value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(float value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(string format, object arg0) + { + TrackWriteOperation(() => base.Write(format, arg0)); + } + + public override void Write(string format, object arg0, object arg1) + { + TrackWriteOperation(() => base.Write(format, arg0, arg1)); + } + + public override void Write(string format, object arg0, object arg1, object arg2) + { + TrackWriteOperation(() => base.Write(format, arg0, arg1, arg2)); + } + + public override void Write(string format, params object[] arg) + { + TrackWriteOperation(() => base.Write(format, arg)); + } + + public override void Write(uint value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void Write(ulong value) + { + TrackWriteOperation(() => base.Write(value)); + } + + public override void WriteLine() + { + TrackWriteOperation(() => base.WriteLine()); + } + + public override void WriteLine(bool value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(char value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(char[] buffer) + { + TrackWriteOperation(() => base.WriteLine(buffer)); + } + + public override void WriteLine(char[] buffer, int index, int count) + { + TrackWriteOperation(() => base.WriteLine(buffer, index, count)); + } + + public override void WriteLine(decimal value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(double value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(int value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(long value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(object value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(float value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(string value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(string format, object arg0) + { + TrackWriteOperation(() => base.WriteLine(format, arg0)); + } + + public override void WriteLine(string format, object arg0, object arg1) + { + TrackWriteOperation(() => base.WriteLine(format, arg0, arg1)); + } + + public override void WriteLine(string format, object arg0, object arg1, object arg2) + { + TrackWriteOperation(() => base.WriteLine(format, arg0, arg1, arg2)); + } + + public override void WriteLine(string format, params object[] arg) + { + TrackWriteOperation(() => base.WriteLine(format, arg)); + } + + public override void WriteLine(uint value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override void WriteLine(ulong value) + { + TrackWriteOperation(() => base.WriteLine(value)); + } + + public override Task WriteLineAsync() + { + return TrackWriteOperationAsync(() => base.WriteLineAsync()); + } + + public IEnumerable Writes() + { + var src = base.GetStringBuilder().ToString(); + foreach (var region in _regions) + { + yield return src.Substring(region.Start, region.Length); + } + } + + public IDisposable Subscribe(IObserver observer) + { + Interlocked.Increment(ref _observerCount); + + return new CompositeDisposable + { + Disposable.Create(() => + { + Interlocked.Decrement(ref _observerCount); + + }), + _writeEvents.Subscribe(observer) + }; + } + + private class TextSpan + { + public int Start { get; init; } + public int Length { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/Pages/Index.razor b/src/Microsoft.TryDotNet.WasmRunner/Pages/Index.razor new file mode 100644 index 000000000..55cc21964 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Pages/Index.razor @@ -0,0 +1,21 @@ +@page "/" +@inject IJSRuntime JS +WasmRunner 9000 + +@code +{ + private CodeRunnerAdapter? _runner; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender) + { + _runner = new CodeRunnerAdapter(new CodeRunner(), JS); + + var runnerReference = DotNetObjectReference.Create(_runner); + + await JS.InvokeVoidAsync("setCodeRunner", runnerReference); + } + } +} diff --git a/src/Microsoft.TryDotNet.WasmRunner/Program.cs b/src/Microsoft.TryDotNet.WasmRunner/Program.cs new file mode 100644 index 000000000..285eb60b9 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Program.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.TryDotNet.WasmRunner; + + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); diff --git a/src/Microsoft.TryDotNet.WasmRunner/Properties/launchSettings.json b/src/Microsoft.TryDotNet.WasmRunner/Properties/launchSettings.json new file mode 100644 index 000000000..04001bb38 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:1654", + "sslPort": 44300 + } + }, + "profiles": { + "Microsoft.TryDotNet.WasmRunner": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7002;http://localhost:5002", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Microsoft.TryDotNet.WasmRunner/RunResults.cs b/src/Microsoft.TryDotNet.WasmRunner/RunResults.cs new file mode 100644 index 000000000..97852f113 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/RunResults.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TryDotNet.WasmRunner; + +public class RunResults +{ + public RunResults(bool succeeded, Exception? exception = null, Exception? runnerException = null) + { + Exception = exception; + Succeeded = succeeded; + RunnerException = runnerException; + } + + public Exception? Exception { get; } + public bool Succeeded { get; } + public Exception? RunnerException { get; } +} \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor b/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor new file mode 100644 index 000000000..824877546 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor @@ -0,0 +1,5 @@ +@inherits LayoutComponentBase + +
+ @Body +
diff --git a/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor.css b/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor.css new file mode 100644 index 000000000..c8654276b --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/Shared/MainLayout.razor.css @@ -0,0 +1,81 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/src/Microsoft.TryDotNet.WasmRunner/_Imports.razor b/src/Microsoft.TryDotNet.WasmRunner/_Imports.razor new file mode 100644 index 000000000..0e9af3ff9 --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/_Imports.razor @@ -0,0 +1,9 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Microsoft.TryDotNet.WasmRunner diff --git a/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/app.css b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/app.css new file mode 100644 index 000000000..9cd148f7d --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/app.css @@ -0,0 +1,64 @@ +@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); + +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +h1:focus { + outline: none; +} + +a, .btn-link { + color: #0071c1; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.content { + padding-top: 1.1rem; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } diff --git a/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..02ae65b5f --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css.map b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css.map new file mode 100644 index 000000000..afcd9e33e --- /dev/null +++ b/src/Microsoft.TryDotNet.WasmRunner/wwwroot/css/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-`