diff --git a/devel-template/TemplatePack.csproj b/devel-template/TemplatePack.csproj index e8de60a9..c6fbd134 100644 --- a/devel-template/TemplatePack.csproj +++ b/devel-template/TemplatePack.csproj @@ -1,16 +1,16 @@ - + Template - 0.1 Microsoft.Performance.ToolKit.Templates + 0.1-preview Microsoft Performance ToolKit Templates Microsoft Corporation Templates for Microsoft Performance ToolKit Plugins. dotnet-new;templates;microsoft;performance netstandard2.1 - + Template true false content diff --git a/devel-template/templates/PluginTemplate/.config/dotnet-tools.json b/devel-template/templates/PluginTemplate/.config/dotnet-tools.json new file mode 100644 index 00000000..c967c89a --- /dev/null +++ b/devel-template/templates/PluginTemplate/.config/dotnet-tools.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "isRoot": true, + "tools": {} +} diff --git a/devel-template/templates/PluginTemplate/.template.config/template.json b/devel-template/templates/PluginTemplate/.template.config/template.json index b771f690..a94fb33d 100644 --- a/devel-template/templates/PluginTemplate/.template.config/template.json +++ b/devel-template/templates/PluginTemplate/.template.config/template.json @@ -6,7 +6,46 @@ "name": "Microsoft Performance ToolKit Plugin", "shortName": "ptkplugin", "tags": { - "language": "C#", - "type": "project" + "language": "C#", + "type": "project" + }, + "postActions": [ + { + "id": "instructions", + "description": "Manual actions required", + "manualInstructions": [ + { + "text": "Open pluginManifest.json in the editor and complete the plugin metadata configuration." + } + ], + "actionId": "AC1156F7-BB77-4DB8-B28F-24EEBCCA1E5C", + "continueOnError": true + } + ], + "symbols": { + "id01": { + "type": "generated", + "generator": "guid", + "replaces": "myid01", + "parameters": { + "defaultFormat": "B" + } + }, + "id02 ": { + "type": "generated", + "generator": "guid", + "replaces": "myid02", + "parameters": { + "defaultFormat ": "B" + } + }, + "id03": { + "type": "generated", + "generator": "guid", + "replaces": "myid03", + "parameters": { + "defaultFormat": "B" + } + } } } diff --git a/devel-template/templates/PluginTemplate/Class1.cs b/devel-template/templates/PluginTemplate/Class1.cs deleted file mode 100644 index c07d8e02..00000000 --- a/devel-template/templates/PluginTemplate/Class1.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Performance.SDK; - -namespace YourNamesapceHere -{ - public class YourPluginHere - { - } -} diff --git a/devel-template/templates/PluginTemplate/Constants.cs b/devel-template/templates/PluginTemplate/Constants.cs new file mode 100644 index 00000000..703fdf4c --- /dev/null +++ b/devel-template/templates/PluginTemplate/Constants.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK.Extensibility; + +namespace SimplePlugin +{ + public static class Constants + { + public const string ParserId = "YourParserId"; + + public const string CookerId = "YourDataCookerId"; + + public static readonly DataCookerPath CookerPath = DataCookerPath.ForSource(ParserId, CookerId); + } +} diff --git a/devel-template/templates/PluginTemplate/PluginTemplate.csproj b/devel-template/templates/PluginTemplate/PluginTemplate.csproj deleted file mode 100644 index d6afcf8c..00000000 --- a/devel-template/templates/PluginTemplate/PluginTemplate.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - netstandard2.1 - - - - - - diff --git a/devel-template/templates/PluginTemplate/SimplePlugin.csproj b/devel-template/templates/PluginTemplate/SimplePlugin.csproj new file mode 100644 index 00000000..bb7ec61f --- /dev/null +++ b/devel-template/templates/PluginTemplate/SimplePlugin.csproj @@ -0,0 +1,26 @@ + + + netstandard2.1 + 0.1.48-preview.g2c726e2004 + https://pkgs.dev.azure.com/perftoolkit/_packaging/PerfToolKit.Developer/nuget/v3/index.json + + + + + + + + + + PreserveNewest + pluginManifest.json + + + + + + + + + + diff --git a/devel-template/templates/PluginTemplate/Tables/WordTable.cs b/devel-template/templates/PluginTemplate/Tables/WordTable.cs new file mode 100644 index 00000000..9032ec2d --- /dev/null +++ b/devel-template/templates/PluginTemplate/Tables/WordTable.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Extensibility; +using Microsoft.Performance.SDK.Processing; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SimplePlugin.Tables +{ + // + // This is a sample regular table that counts the characters in each word of a .txt file. + // It also provides a column (word) that can be grouped on. + // + + // + // Add a Table attribute in order for the CustomDataSourceBase to understand your table. + // + + [Table] + + // + // Have the MetadataTable inherit the custom TableBase class + // + + public sealed class WordTable + { + public static TableDescriptor TableDescriptor => new TableDescriptor( + Guid.Parse("myid02"), // The GUID must be unique across all tables + "Word Stats", // The Table must have a name + "Statistics for words", // The Table must have a description + "Words", // A category is optional. It useful for grouping different types of tables in the viewer's UI. + requiredDataCookers: new List + { + Constants.CookerPath + }); + + // + // Declare columns here. You can do this using the ColumnConfiguration class. + // It is possible to declaratively describe the table configuration as well. Please refer to our Advanced Topics Wiki page for more information. + // + // The Column metadata describes each column in the table. + // Each column must have a unique GUID and a unique name. The GUID must be unique globally; the name only unique within the table. + // + // The UIHints provides some hints to a viewer (such as WPA) on how to render the column. + // In this sample, we are simply saying to allocate at least 80 units of width. + // + + private static readonly ColumnConfiguration FileColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{91C69594-4079-4478-B1A0-3FEC01641D8D}"), "File", "The file containing the word."), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration WordColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{A669FC83-BF61-4604-8BB2-44E66FCA7062}"), "Word", "The actual word."), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration CharacterCountColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{E3056A08-D44D-4CD6-8158-503BDAEF899C}"), "Character Count", "The number of characters in the word."), + new UIHints { Width = 80 }); + + private static readonly ColumnConfiguration TimeColumn = new ColumnConfiguration( + new ColumnMetadata(new Guid("{54B4A016-9F78-4BAE-A0AB-AFDFBF33C3F1}"), "Time", "The time when the word is written to the file."), + new UIHints { Width = 80 }); + + + // + // This method, with this exact signature, is required so that the runtime can + // build your table once all cookers have processed their data. + // + public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieval tableData) + { + // + // Implement your columns here. + // Columns are implemented via Projections, which are simply functions that map a row index to a data point. + // + + // Query the data cooker output for the data you need to build your columns. + var lines = tableData.QueryOutput>>>( + new DataOutputPath(Constants.CookerPath, nameof(WordDataCooker.Lines))); + + var allWords = new List>(); + foreach (var kvp in lines) + { + foreach (var tuple in kvp.Value) + { + allWords.Add(Tuple.Create(kvp.Key, tuple.Item1, tuple.Item2)); + } + } + + var startTime = allWords.Min(x => x.Item2); + var allWordsRelative = allWords.Select(x => Tuple.Create(x.Item1, Timestamp.FromNanoseconds((x.Item2 - startTime).ToNanoseconds), x.Item3)).ToList(); + + // + // Use Projection.Index() to get a base projection from row index to a tuple. + // + + var baseProjection = Projection.Index(allWordsRelative); + + // + // Create projection for each column by composing the base projection with another projection that maps to the data point as needed. + // + + var fileProjection = baseProjection.Compose(x => x.Item1); + var wordProjection = baseProjection.Compose(x => x.Item3); + var charCountProjection = baseProjection.Compose(x => x.Item3.Length); + var timeProjection = baseProjection.Compose(x => x.Item2); + + // + // Table Configurations describe how your table should be presented to the user: + // the columns to show, what order to show them, which columns to aggregate, and which columns to graph. + // You may provide a number of columns in your table, but only want to show a subset of them by default so as not to overwhelm the user. + // The table configuration class also exposes four (4) columns that viewers can recognize: Pivot Column, Graph Column, Left Freeze Column, Right Freeze Column + // For more information about what these columns do, go to "Advanced Topics" -> "Table Configuration" in our Wiki. Link can be found in README.md + // + + var config = new TableConfiguration("Word Time") + { + Columns = new[] + { + WordColumn, + TableConfiguration.PivotColumn, + FileColumn, + CharacterCountColumn, + TableConfiguration.GraphColumn, + TimeColumn, + }, + }; + + // + // Column roles describe what columns should be used for various graphing properties. For + // this table, the only graphing column is the time column, which should be used as the + // start time of each row during graphing. + // + config.AddColumnRole(ColumnRole.StartTime, TimeColumn); + + // + // + // Use the table builder to build the table. + // Add and set table configuration if applicable. + // Then set the row count (we have one row per word) and then add the columns using AddColumn. + // + + tableBuilder.AddTableConfiguration(config) + .SetDefaultTableConfiguration(config) + .SetRowCount(allWordsRelative.Count) + .AddColumn(FileColumn, fileProjection) + .AddColumn(WordColumn, wordProjection) + .AddColumn(CharacterCountColumn, charCountProjection) + .AddColumn(TimeColumn, timeProjection); + + } + } +} diff --git a/devel-template/templates/PluginTemplate/WordDataCooker.cs b/devel-template/templates/PluginTemplate/WordDataCooker.cs new file mode 100644 index 00000000..6d6971ea --- /dev/null +++ b/devel-template/templates/PluginTemplate/WordDataCooker.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Extensibility; +using Microsoft.Performance.SDK.Extensibility.DataCooking; +using Microsoft.Performance.SDK.Extensibility.DataCooking.SourceDataCooking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace SimplePlugin +{ + public class WordDataCooker + : SourceDataCooker + { + private Dictionary>> lines; + + public WordDataCooker() + : base(Constants.CookerPath) + { + this.lines = new Dictionary>>(); + } + + [DataOutput] + public IReadOnlyDictionary>> Lines => + this.lines.ToDictionary(x => x.Key, x => x.Value as IReadOnlyList>); + + public override SourceDataCookerOptions Options => SourceDataCookerOptions.ReceiveAllDataElements; + + public override string Description => "I store words."; + + public override ReadOnlyHashSet DataKeys => new ReadOnlyHashSet(new HashSet()); + + public override DataProcessingResult CookDataElement(WordEvent data, WordSourceParser context, CancellationToken cancellationToken) + { + if (!this.lines.TryGetValue(data.FilePath, out var value)) + { + value = new List>(); + this.lines.Add(data.FilePath, value); + } + + value.Add(Tuple.Create(data.Time, data.Word)); + + return DataProcessingResult.Processed; + } + } +} diff --git a/devel-template/templates/PluginTemplate/WordEvent.cs b/devel-template/templates/PluginTemplate/WordEvent.cs new file mode 100644 index 00000000..7e6e217b --- /dev/null +++ b/devel-template/templates/PluginTemplate/WordEvent.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Extensibility; + +namespace SimplePlugin +{ + public class WordEvent + : IKeyedDataType + { + public string Word { get; } + + public Timestamp Time { get; } + + public string FilePath { get; } + + public WordEvent(string word, Timestamp timestamp, string filePath) + { + this.Word = word; + this.Time = timestamp; + this.FilePath = filePath; + } + + public string GetKey() + { + return this.Word; // TODO: update key if needed. + } + } +} diff --git a/devel-template/templates/PluginTemplate/WordSourceParser.cs b/devel-template/templates/PluginTemplate/WordSourceParser.cs new file mode 100644 index 00000000..67936544 --- /dev/null +++ b/devel-template/templates/PluginTemplate/WordSourceParser.cs @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK; +using Microsoft.Performance.SDK.Extensibility.SourceParsing; +using Microsoft.Performance.SDK.Processing; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace SimplePlugin +{ + public class WordSourceParser + : ISourceParser + { + private readonly string[] filePaths; + + public WordSourceParser(string[] fileNames) + { + this.filePaths = fileNames; + } + + // Used to tell the SDK the time range of the data (if applicable) and any other relevant data for rendering / synchronizing. + // This gets updated as we process the files + private DataSourceInfo dataSourceInfo; + + public DataSourceInfo DataSourceInfo => this.dataSourceInfo; + + public string Id => Constants.ParserId; + + public Type DataElementType => typeof(WordEvent); + + public Type DataContextType => typeof(WordSourceParser); + + public Type DataKeyType => typeof(string); + + // For this example, we do not need to parse more than once + public int MaxSourceParseCount => 1; + + public void PrepareForProcessing(bool allEventsConsumed, IReadOnlyCollection requestedDataKeys) + { + // NOOP here; process everything :) + } + + public void ProcessSource(ISourceDataProcessor dataProcessor, ILogger logger, IProgress progress, CancellationToken cancellationToken) + { + // + // This is where you add your own logic to process the data into a format for your tables. + // + // In this sample, our tables will operate on a collection of lines in the file + // so we read all of the lines of the file and store them into a backing dictionary field. + // + // ProcessAsync is also where you would determine the information necessary for GetDataSourceInfo(). + // In this sample, we take down the start and stop timestamps from the files + // + // Note: if you must do processing based on which tables are enabled, you would check the EnabledTables property + // (provided in the base class) on your class to see what you should do. For example, a processing source with + // many disjoint tables may look at what tables are enabled in order to turn on only specific processors to avoid + // processing everything if it doesn't have to. + // + + // Timestamp relative to the first event time from all lines + Timestamp startTime = Timestamp.MaxValue; + + // Timestamp relative to the last event from all lines + Timestamp endTime = Timestamp.MinValue; + + // The time of the first event from all lines + DateTime firstEvent = DateTime.MinValue; + + // The final processed data we are building, with correct (relative) Timestamp values + var relativeContentDictionary = new Dictionary>>(); + + // Used to help calculate progress + int nFiles = this.filePaths.Length; + var currentFile = 0; + + // + // In this sample, we are parsing each file in-memory inside of our ProcessAsyncCore method. It is possible to delegate + // the task of processing a file to a custom Parser object by extending CustomDataProcessorBaseWithSourceParser + // instead of CustomDataProcessorBase. See the advanced samples for more information. + // + + foreach (var path in this.filePaths) + { + var content = System.IO.File.ReadAllLines(path); + + // Used to help calculate progress + int nLines = content.Length; + var currentLine = 0; + + foreach (var line in content) + { + var items = line.Split(new[] { ',' }, 2); + + // + // Validate input. Any exceptions thrown while processing data sources bubbled up to the caller + // (outside the SDK) who asked the data sources to be processed. + // + + if (items.Length < 2) + { + throw new InvalidOperationException("File line cannot be split to two sub-strings"); + } + + DateTime time; + if (!DateTime.TryParse(items[0], out time)) + { + throw new InvalidOperationException("Time cannot be pasred to DateTime format"); + } + + var timeStamp = Timestamp.FromNanoseconds(time.Ticks * 100); + var words = items[1].Split(" ", StringSplitOptions.RemoveEmptyEntries); + + if (timeStamp < startTime) + { + startTime = timeStamp; + firstEvent = time; + } + + if (timeStamp > endTime) + { + endTime = timeStamp; + } + + foreach (var word in words) + { + dataProcessor.ProcessDataElement(new WordEvent(word, timeStamp, path), this, cancellationToken); + } + + // Reporting progress is optional, but recommended + progress.Report(CalculateProgress(currentLine, currentFile, nLines, nFiles)); + ++currentLine; + } + + progress.Report(CalculateProgress(currentLine, currentFile, nLines, nFiles)); + ++currentFile; + } + + // startTime is calculated from firstEvent in the above for loop, so our first event timestamp + // will always be 0. + this.dataSourceInfo = new DataSourceInfo(0, (endTime - startTime).ToNanoseconds, firstEvent.ToUniversalTime()); + + progress.Report(100); + } + + private int CalculateProgress(int currentLine, int currentFile, int nLines, int nFiles) + { + double completedFilesWeight = (double)currentFile; + + double completedLinesWeight = (double)currentLine / nLines; + + double percentComplete = (completedFilesWeight + completedLinesWeight) / nFiles; + Console.WriteLine(percentComplete); + return (int)(percentComplete * 100.0); + } + } +} diff --git a/devel-template/templates/PluginTemplate/WordsDataProcessorWithSourceParser.cs b/devel-template/templates/PluginTemplate/WordsDataProcessorWithSourceParser.cs new file mode 100644 index 00000000..2cdc9832 --- /dev/null +++ b/devel-template/templates/PluginTemplate/WordsDataProcessorWithSourceParser.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK.Extensibility.SourceParsing; +using Microsoft.Performance.SDK.Processing; + +namespace SimplePlugin +{ + internal class WordsDataProcessorWithSourceParser + : CustomDataProcessorWithSourceParser + { + public WordsDataProcessorWithSourceParser( + ISourceParser sourceParser, + ProcessorOptions options, + IApplicationEnvironment applicationEnvironment, + IProcessorEnvironment processorEnvironment) + : base(sourceParser, options, applicationEnvironment, processorEnvironment) + { + } + } +} diff --git a/devel-template/templates/PluginTemplate/WordsProcessingSource.cs b/devel-template/templates/PluginTemplate/WordsProcessingSource.cs new file mode 100644 index 00000000..72e54273 --- /dev/null +++ b/devel-template/templates/PluginTemplate/WordsProcessingSource.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.SDK.Processing; +using System.Collections.Generic; +using System.Linq; + +namespace SimplePlugin +{ + // + // This is a sample Processing Source that understands files with the .txt extension + // + + // + // In order for a processing source to be recognized, it MUST satisfy the following: + // a) Be a public type + // b) Have a public parameterless constructor + // c) Implement the IProcessingSource interface + // d) Be decorated with the ProcessingSourceAttribute attribute + // e) Be decorated with at least one of the derivatives of the DataSourceAttribute attribute + // + + [ProcessingSource( + "myid01", // The GUID must be unique for your Processing Source. You can use Visual Studio's Tools -> Create Guid… tool to create a new GUID + "Simple Data Source", // The Processing Source MUST have a name + "A data source to count words!")] // The Processing Source MUST have a description + [FileDataSource( + ".txt", // A file extension is REQUIRED + "Text files")] // A description is OPTIONAL. The description is what appears in the file open menu to help users understand what the file type actually is. + + // + // There are two methods to creating a Processing Source that is recognized by the SDK: + // 1. Using the helper abstract base classes + // 2. Implementing the raw interfaces + // This sample demonstrates method 1 where the CustomDataSourceBase abstract class + // helps provide a public parameterless constructor and implement the ICustomDataSource interface + // + + public class WordsProcessingSource + : ProcessingSource + { + private IApplicationEnvironment applicationEnvironment; + + // + // Provides information about this Processing Source, such as the author, + // a project link, licensing, etc. This information can be used by tools + // to display "About" information for your Processing Source. For example, + // Windows Performance Analyzer (WPA) uses this information for Help->About. + // + + public override ProcessingSourceInfo GetAboutInfo() + { + return new ProcessingSourceInfo + { + // + // The copyright notice for this Processing Source. + // + CopyrightNotice = "Copyright 2021 Microsoft Corporation. All Rights Reserved.", + + // + // The license under which this Processing Source may be used. + // + LicenseInfo = new LicenseInfo + { + Name = "MIT", + Text = "Please see the link for the full license text.", + Uri = "https://github.com/microsoft/microsoft-performance-toolkit-sdk/blob/main/LICENSE.txt", + }, + + // + // A collection of the people or entities that own this Processing Source. + // + Owners = new[] + { + new ContactInfo + { + Address = "1 Microsoft Way, Redmond, WA 98052", + EmailAddresses = new[] + { + "noreply@microsoft.com", + }, + }, + }, + + // + // Information, if applicable, of where users can find this project. + // + ProjectInfo = new ProjectInfo + { + Uri = "https://github.com/microsoft/microsoft-performance-toolkit-sdk", + }, + + // + // Any additional information you wish your users to know about this Processing Source. + // + AdditionalInformation = new[] + { + "This Processing Source is a sample showcasing the Performance Toolkit SDK.", + } + }; + } + + protected override void SetApplicationEnvironmentCore(IApplicationEnvironment applicationEnvironment) + { + // + // Saves the given application environment into this instance. This samples does not directly use this value, + // but it can be used to perform various application-specific actions such as: + // - presenting dialog boxes + // - refreshing tables + // - serializing table configurations + // See advanced tutorials for more information + // + + this.applicationEnvironment = applicationEnvironment; + } + + protected override ICustomDataProcessor CreateProcessorCore( + IEnumerable dataSources, + IProcessorEnvironment processorEnvironment, + ProcessorOptions options) + { + // + // Create a new instance of a class implementing ICustomDataProcessor here to process the specified data sources. + // Note that you can have more advanced logic here to create different processors if you would like based on the source, or any other criteria. + // You are not restricted to always returning the same type from this method. + // + + return new WordsDataProcessorWithSourceParser( + new WordSourceParser(dataSources.Select(x => x.Uri.LocalPath).ToArray()), + options, + this.applicationEnvironment, + processorEnvironment); + } + + protected override bool IsDataSourceSupportedCore(IDataSource source) + { + // + // This method is called for every data source which matches the one declared in the DataSource attribute. It may be useful + // to peek inside the source to truly determine if you can support it, especially if your data processor supports a common + // filetype like .txt or .csv. + // For this sample, we'll always return true for simplicity. + // + + return true; + } + } +} diff --git a/devel-template/templates/PluginTemplate/pluginManifest.json b/devel-template/templates/PluginTemplate/pluginManifest.json index c7aa0fe4..815ef4c6 100644 --- a/devel-template/templates/PluginTemplate/pluginManifest.json +++ b/devel-template/templates/PluginTemplate/pluginManifest.json @@ -6,6 +6,7 @@ }, "displayName": "__PLUGIN_DISPLAY_NAME__", "description": "__PLUGIN_DESCRIPTION__", + "displayName": "__PLUGIN_DISPLAY_NAME__", "owners": [ { "name": "__PLUGIN_OWNER_NAME__", diff --git a/devel-template/templates/PluginTemplate/scripts/PackageToPlugin.cmd b/devel-template/templates/PluginTemplate/scripts/PackageToPlugin.cmd new file mode 100644 index 00000000..09d45c3e --- /dev/null +++ b/devel-template/templates/PluginTemplate/scripts/PackageToPlugin.cmd @@ -0,0 +1,39 @@ +@echo off + +set "buildDirectory=%~1" +set "toolVersion=%~2" +set "toolPackagePath=%~3" +set "outputDirectory=%~4" + +set "toolName=plugintool" +set "toolCommand=plugin" + +for /f "tokens=1,2" %%a in ('dotnet tool list') do ( + if "%%a"=="%toolName%" ( + if "%%b"=="%toolVersion%" ( + echo %toolName% version %toolVersion% is already installed. + goto :run + ) else ( + echo Found %toolName% with a different version: %%b + echo Uninstalling %%b... + dotnet tool uninstall %toolName% + ) + ) +) + +echo Installing %toolName% version %toolVersion%... +dotnet tool install --add-source %toolPackagePath% %toolName% --version %toolVersion% + +:run +echo Running %toolName% %buildDirectory% %outputDirectory%... + +dotnet %toolCommand% pack -s %buildDirectory% -t %outputDirectory% -b 2>nul + +if %ERRORLEVEL% equ 0 ( + echo mytool executed successfully! +) else ( + echo mytool encountered an error. + exit /b 1 +) + +exit /b 0 diff --git a/devel-template/version.json b/devel-template/version.json new file mode 100644 index 00000000..b379d67d --- /dev/null +++ b/devel-template/version.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1-preview", + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/heads/release/v?\\d+(?:\\.\\d+)?$" + ], + "nugetPackageVersion": { + "semVer": 2 + }, + "pathFilters": [ + "." + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + } +}