* Add CLI

* Add push

* Update assembly name

* Add json schema doc

* Add components doc

* Add CLI doc

* Fix doc

* Fix doc 2

* wip

* Fix metadata generator

* Update package extension to .ptix

* Remove old projeact

* Delete and untrack launchsettings.json

* Print errors

* Update extension name to ptix

* Require Id and Version

* Put cli and publisher into a folder and don't interfere with plugins system versioning

* Add manifest schema

* Exclude schema folder

* Update plugin project template

* Update metadata gen

* Clean up metadata generation

* Fix schema comma

* Add manifest schema validation

* Validate using data annotations

* Update pack

* Pack as dotnet tool

* Change publisher location

* Resolve conflicts

* Fix contents metadata

* Remove version from schema

* Add manifestVersion to required

* Move package stuff to its own project

* Fix issues in cli

* Update plugin template

* Undo change in .gitignore

* Untrack launchSettings.json

* Update to use json validation

* Update schema location

* Add "bundled manifest" option

* Fix shouldinclude

* Update template

* Update plugin template

* Rename template

* Fix path issues

* Revert changes in templates and samples

* Remove publisher project

* Remove added files in samples

* Remove schema folder

* Remove schema md doc

* Add comments in validator

* Update source directory validation to check SDK

* Refactor options classes

* Handles destination overwrites

* Undo package change

* Remove package folder

* Undo change in ZipPluginPackage

* Add schama loader

* Clean up program.cs

* Add common options

* Add args validation and exception types

* Clean ups

* Add eof new lines and copy rights

* Remove .md files

* Remove unwanted files

* Remove generated xml

* Clean up constants.cs

* Use classes for each options instead of service locator

* Fix property name

* Separate options validation

* Decoupled commands and console

* Updated error handling

* Clean up

* Add manifest locators

* Address comments for utils method

* Move options validation to separate classes

* Address more comments

* Added base command

* Update protected to private

* Decorate with NotNullWhen

* Add docstrings

* Remove empty lines and fix warning

* Update package name and tool name

* Update helptext and add basic documentation

* Continue processing if BadImageFormatException is encountered during the scan

* Adrress more comments

* Use VersionChecker to access sdk versions

* Track all versions that have been checked and log version check errors

* Update logging based on pr comments
This commit is contained in:
helenkzhang 2023-09-06 14:01:32 -07:00 коммит произвёл GitHub
Родитель 6330c1c14f
Коммит ab49926ec5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
47 изменённых файлов: 2275 добавлений и 16 удалений

Просмотреть файл

@ -0,0 +1,28 @@
## Plugintool CLI
The `plugintool` cli can be used to generate plugin metadata files (`metadata.json` and `metadatacontents.json`) and pack plugin binaries into `.ptix` packages.
### Usage
#### Metadata Files Generation
`plugintool metadata-gen [-s|--source <SOURCE_DIR>] [-o|--output <OUTPUT_DIR>] [-m|--manifest <MANIFEST_FILE>] [-w|--overwrite]`
- `-s|--source <SOURCE_DIR>` (Required)
Specifies the directory containing the plugin binaries.
- `-o|--output <OUTPUT_DIR>`
Specifies the directory where the metadata files will be created. If not provided, the files will be created in the current directory.
- `-m|--manifest <MANIFEST_FILE>`
Specifies the path to the manifest file. If not provided, the tool will look for a `pluginManifest.json` file in the source directory.
- `-w|--overwrite`
Specifies whether to overwrite existing metadata files if they exist. It's only valid if the `-o|--output` option is specified.
#### Package a Plugin
`plugintool pack [-s|--source <SOURCE_DIR>] [-o|--output <OUTPUT_FILE_PATH>] [-m|--manifest <MANIFEST_FILE>] [-w|--overwrite]` `
- `-s|--source <SOURCE_DIR>` (Required)
Specifies the directory containing the plugin binaries.
- `-o|--output <OUTPUT_FILE_PATH>`
Specifies the path where the `.ptix` package will be created. If not provided, the package will be created in the current directory.
- `-m|--manifest <MANIFEST_FILE>`
Specifies the path to the manifest file. If not provided, the tool will look for a `pluginManifest.json` file in the source directory.
- `-w|--overwrite`
Specifies whether to overwrite existing metadata files if they exist. It's only valid if the `-o|--output` option is specified.

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Concurrent;
using System.Collections.Generic;
using NuGet.Versioning;
namespace Microsoft.Performance.SDK.Runtime
{
/// <summary>
/// A <see cref="VersionChecker"/> that keeps track of the versions it has checked and whether they are supported.
/// </summary>
public class TrackingVersionChecker
: VersionChecker
{
private readonly ConcurrentDictionary<SemanticVersion, bool> checkedVersion;
/// <summary>
/// Initializes a new instance of the <see cref="TrackingVersionChecker"/>
/// </summary>
public TrackingVersionChecker()
{
this.checkedVersion = new ConcurrentDictionary<SemanticVersion, bool>();
}
/// <summary>
/// Gets the list of versions that have been checked by this instance.
/// </summary>
public IReadOnlyDictionary<SemanticVersion, bool> CheckedVersions
{
get
{
return this.checkedVersion;
}
}
/// <summary>
/// Overrides the base implementation to keep track of the versions that have been checked.
/// </summary>
/// <param name="candidateVersion">
/// The version to check.
/// </param>
/// <returns>
/// <c>true</c> if the version is supported; <c>false</c> otherwise.
/// </returns>
public override bool IsVersionSupported(SemanticVersion candidateVersion)
{
bool supported = base.IsVersionSupported(candidateVersion);
this.checkedVersion.TryAdd(candidateVersion, supported);
return supported;
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Core.Metadata
{
/// <summary>
/// Contains the names of the non-file data sources that are supported by the toolkit
/// </summary>
public static class DataSourceNameConstants
{
public const string DirectoryDataSourceName = "directory";
public const string ExtensionlessDataSourceName = "extensionless";
}
}

Просмотреть файл

@ -11,5 +11,6 @@ namespace Microsoft.Performance.Toolkit.Plugins.Runtime.Package
public const string PluginMetadataFileName = "metadata.json";
public const string PluginContentsMetadataFileName = "contentsmetadata.json";
public const string PluginContentFolderName = "plugin/";
public const string PluginPackageExtension = ".ptix";
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// Common arguments for packgen commands.
/// </summary>
/// <param name="SourceDirectoryFullPath">
/// The full path to the directory containing the plugin.
/// </param>
/// <param name="ManifestFileFullPath">
/// The full path to the manifest file to use.
/// </param>
/// <param name="Overwrite">
/// Whether or not to overwrite an existing output file.
/// </param>
internal record PackGenCommonArgs(
string SourceDirectoryFullPath,
string? ManifestFileFullPath,
bool Overwrite);
}

Просмотреть файл

@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest;
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common
{
/// <summary>
/// Base class for commands that require the plugin to be processed.
/// </summary>
/// <typeparam name="TArgs">
/// The type of arguments for the command.
/// </typeparam>
internal abstract class PackGenCommonCommand<TArgs>
: ICommand<TArgs>
where TArgs : PackGenCommonArgs
{
protected readonly IManifestLocatorFactory manifestLocatorFactory;
protected readonly IPluginArtifactsProcessor artifactsProcessor;
protected readonly ILogger<PackGenCommonCommand<TArgs>> logger;
protected PackGenCommonCommand(
IManifestLocatorFactory manifestLocatorFactory,
IPluginArtifactsProcessor artifactsProcessor,
ILogger<PackGenCommonCommand<TArgs>> logger)
{
this.manifestLocatorFactory = manifestLocatorFactory;
this.artifactsProcessor = artifactsProcessor;
this.logger = logger;
}
/// <inheritdoc />
public int Run(TArgs args)
{
if (!TryGetProcessedPluginResult(args, out ProcessedPluginResult? processedSource))
{
return 1;
}
return RunCore(args, processedSource!);
}
protected abstract int RunCore(TArgs args, ProcessedPluginResult processedSource);
private bool TryGetProcessedPluginResult(PackGenCommonArgs args, [NotNullWhen(true)] out ProcessedPluginResult? processedPluginResult)
{
processedPluginResult = null;
IManifestLocator manifestLocator = this.manifestLocatorFactory.Create(args);
if (!manifestLocator.TryLocate(out string? manifestFilePath))
{
this.logger.LogError("Failed to locate manifest file.");
return false;
}
var artifacts = new PluginArtifacts(args.SourceDirectoryFullPath, manifestFilePath);
if (!this.artifactsProcessor.TryProcess(artifacts, out processedPluginResult))
{
this.logger.LogError("Failed to process plugin artifacts.");
return false;
}
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common
{
/// <summary>
/// Base class for validating common packgen options.
/// </summary>
/// <typeparam name="TOptions">
/// The type of options to validate.
/// </typeparam>
/// <typeparam name="TArgs">
/// The type of arguments to return.
/// </typeparam>
internal abstract class PackGenCommonOptionsValidator<TOptions, TArgs>
: IOptionsValidator<TOptions, TArgs>
where TOptions : PackGenCommonOptions
where TArgs : PackGenCommonArgs
{
protected readonly ILogger<PackGenCommonOptionsValidator<TOptions, TArgs>> logger;
protected PackGenCommonOptionsValidator(ILogger<PackGenCommonOptionsValidator<TOptions, TArgs>> logger)
{
this.logger = logger;
}
/// <inheritdoc />
public bool TryValidate(TOptions cliOptions, [NotNullWhen(true)] out TArgs? validatedAppArgs)
{
if (!TryValidateCommonOptions(cliOptions, out PackGenCommonArgs validatedCommonArgs))
{
validatedAppArgs = null;
return false;
}
return TryValidateCore(cliOptions, validatedCommonArgs, out validatedAppArgs);
}
protected abstract bool TryValidateCore(TOptions options, PackGenCommonArgs validatedCommonAppArgs, out TArgs? validatedAppArgs);
private bool TryValidateCommonOptions(
PackGenCommonOptions rawOptions,
out PackGenCommonArgs validatedAppArgs)
{
validatedAppArgs = null!;
if (string.IsNullOrWhiteSpace(rawOptions.SourceDirectory))
{
this.logger.LogError("Source directory must be specified. Use --source <path> or -s <path>.");
return false;
}
if (!Directory.Exists(rawOptions.SourceDirectory))
{
this.logger.LogError($"Source directory '{rawOptions.SourceDirectory}' does not exist.");
return false;
}
string sourceDirectoryFullPath = Path.GetFullPath(rawOptions.SourceDirectory);
// Validate manifest file path
string? manifestFileFullPath = null;
if (rawOptions.ManifestFilePath != null)
{
if (!File.Exists(rawOptions.ManifestFilePath))
{
this.logger.LogError($"Manifest file '{rawOptions.ManifestFilePath}' does not exist.");
return false;
}
manifestFileFullPath = Path.GetFullPath(rawOptions.ManifestFilePath);
}
validatedAppArgs = new PackGenCommonArgs(sourceDirectoryFullPath, manifestFileFullPath, rawOptions.Overwrite);
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// Represents a command that can be executed via the CLI.
/// </summary>
/// <typeparam name="TArgs">
/// The type of arguments that the command accepts.
/// </typeparam>
internal interface ICommand<TArgs>
where TArgs : class
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="args">
/// The arguments to the command.
/// </param>
/// <returns>
/// The exit code of the command. A value of 0 indicates success. A value of 1 indicates failure.
/// </returns>
int Run(TArgs args);
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// Represents the arguments for the <see cref="MetadataGenCommand"/>.
/// </summary>
/// <param name="SourceDirectoryFullPath">
/// The full path to the directory containing the source code for the plugin.
/// </param>
/// <param name="ManifestFileFullPath">
/// The full path to the manifest file for the plugin.
/// </param>
/// <param name="OutputDirectoryFullPath">
/// The full path to the directory to write the generated metadata to.
/// </param>
/// <param name="Overwrite">
/// Whether or not to overwrite existing files in the output directory.
/// </param>
internal record MetadataGenArgs(
string SourceDirectoryFullPath,
string? ManifestFileFullPath,
string? OutputDirectoryFullPath,
bool Overwrite)
: PackGenCommonArgs(
SourceDirectoryFullPath,
ManifestFileFullPath,
Overwrite)
{
/// <summary>
/// Initializes a new instance of the <see cref="MetadataGenArgs"/> class.
/// </summary>
/// <param name="commonArgs">
/// The common arguments.
/// </param>
/// <param name="outputDirectoryFullPath">
/// The full path to the directory to write the generated metadata.
/// </param>
public MetadataGenArgs(PackGenCommonArgs commonArgs, string? outputDirectoryFullPath)
: this(commonArgs.SourceDirectoryFullPath, commonArgs.ManifestFileFullPath, outputDirectoryFullPath, commonArgs.Overwrite)
{
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common;
using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest;
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
using Microsoft.Performance.Toolkit.Plugins.Core.Metadata;
using Microsoft.Performance.Toolkit.Plugins.Core.Serialization;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// Command to generate plugin metadata and contents metadata.
/// </summary>
internal class MetadataGenCommand
: PackGenCommonCommand<MetadataGenArgs>
{
private readonly ISerializer<PluginMetadata> metadataSerializer;
private readonly ISerializer<PluginContentsMetadata> contentsMetadataSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="MetadataGenCommand"/>
/// </summary>
/// <param name="manifestLocatorFactory">
/// The manifest locator factory to use to create manifest locators.
/// </param>
/// <param name="artifactsProcessor">
/// The artifacts processor to use to process plugin artifacts.
/// </param>
/// <param name="metadataSerializer">
/// The serializer to use to serialize the plugin metadata.
/// </param>
/// <param name="contentsMetadataSerializer">
/// The serializer to use to serialize the plugin contents metadata.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public MetadataGenCommand(
IManifestLocatorFactory manifestLocatorFactory,
IPluginArtifactsProcessor artifactsProcessor,
ISerializer<PluginMetadata> metadataSerializer,
ISerializer<PluginContentsMetadata> contentsMetadataSerializer,
ILogger<MetadataGenCommand> logger)
: base(manifestLocatorFactory, artifactsProcessor, logger)
{
this.metadataSerializer = metadataSerializer;
this.contentsMetadataSerializer = contentsMetadataSerializer;
}
protected override int RunCore(MetadataGenArgs args, ProcessedPluginResult processedSource)
{
PluginMetadata metadata = processedSource.Metadata;
PluginContentsMetadata contentsMetadata = processedSource.ContentsMetadata;
bool outputSpecified = args.OutputDirectoryFullPath != null;
string? outputDirectory = outputSpecified ? args.OutputDirectoryFullPath : Environment.CurrentDirectory;
bool shouldOverwrite = outputSpecified && args.Overwrite;
string destMetadataFileName = Path.Combine(outputDirectory!, $"{metadata.Identity}-{PackageConstants.PluginMetadataFileName}");
string validDestMetadataFileName = shouldOverwrite ?
destMetadataFileName : Utils.GetAlterDestFilePath(destMetadataFileName);
string destContentsMetadataFileName = Path.Combine(outputDirectory!, $"{metadata.Identity}-{PackageConstants.PluginContentsMetadataFileName}");
string validDestContentsMetadataFileName = shouldOverwrite ?
destContentsMetadataFileName : Utils.GetAlterDestFilePath(destContentsMetadataFileName);
using (FileStream fileStream = File.Open(validDestMetadataFileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
this.metadataSerializer.Serialize(fileStream, metadata);
}
this.logger.LogInformation($"Successfully created plugin metadata at {validDestMetadataFileName}.");
using (FileStream fileStream = File.Open(validDestContentsMetadataFileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
this.contentsMetadataSerializer.Serialize(fileStream, contentsMetadata);
}
this.logger.LogInformation($"Successfully created plugin contents metadata at {validDestContentsMetadataFileName}.");
return 0;
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands.MetadataGen
{
/// <summary>
/// Validates <see cref="MetadataGenOptions"/> and converts them to <see cref="MetadataGenArgs"/>.
/// </summary>
internal class MetadataGenOptionsValidator
: PackGenCommonOptionsValidator<MetadataGenOptions, MetadataGenArgs>
{
/// <summary>
/// Initializes a new instance of the <see cref="MetadataGenOptionsValidator"/>
/// </summary>
/// <param name="logger">
/// Logger to use for validation.
/// </param>
public MetadataGenOptionsValidator(ILogger<MetadataGenOptionsValidator> logger)
: base(logger)
{
}
protected override bool TryValidateCore(MetadataGenOptions cliOptions, PackGenCommonArgs validatedCommonAppArgs, out MetadataGenArgs? validatedAppArgs)
{
validatedAppArgs = null;
if (cliOptions.OutputDirectory == null && cliOptions.Overwrite)
{
this.logger.LogError("Cannot overwrite output directory when output directory is not specified.");
return false;
}
string? outputDirectoryFullPath = null;
if (cliOptions.OutputDirectory != null)
{
if (!Directory.Exists(cliOptions.OutputDirectory))
{
this.logger.LogError($"Output directory '{cliOptions.OutputDirectory}' does not exist." +
$"Please create it or omit the --output option to use the current directory.");
return false;
}
outputDirectoryFullPath = Path.GetFullPath(cliOptions.OutputDirectory);
}
validatedAppArgs = new MetadataGenArgs(validatedCommonAppArgs, outputDirectoryFullPath);
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// Represents the arguments for the <see cref="PackCommand"/>.
/// </summary>
/// <param name="SourceDirectoryFullPath">
/// The full path to the directory containing the plugin to pack.
/// </param>
/// <param name="ManifestFileFullPath">
/// The full path to the manifest file.
/// </param>
/// <param name="OutputFileFullPath">
/// The full path to where the output package file should be written.
/// </param>
/// <param name="OverWrite">
/// Whether or not to overwrite the output file if it already exists.
/// </param>
internal record PackArgs(
string SourceDirectoryFullPath,
string? ManifestFileFullPath,
string? OutputFileFullPath,
bool OverWrite)
: PackGenCommonArgs(SourceDirectoryFullPath, ManifestFileFullPath, OverWrite)
{
/// <summary>
/// Initializes a new instance of the <see cref="PackArgs"/>
/// </summary>
/// <param name="commonArgs">
/// The common arguments.
/// </param>
/// <param name="outputFileFullPath">
/// The full path to the output package file.
/// </param>
public PackArgs(PackGenCommonArgs commonArgs, string? outputFileFullPath)
: this(commonArgs.SourceDirectoryFullPath, commonArgs.ManifestFileFullPath, outputFileFullPath, commonArgs.Overwrite)
{
}
}
}

Просмотреть файл

@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
using Microsoft.Performance.Toolkit.Plugins.Cli.Packaging;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands
{
/// <summary>
/// This command is used to pack a plugin into a plugin package.
/// </summary>
internal sealed class PackCommand
: PackGenCommonCommand<PackArgs>
{
private readonly IPackageBuilder packageBuilder;
/// <summary>
/// Creates a new instance of <see cref="PackCommand"/>.
/// </summary>
/// <param name="manifestLocatorFactory">
/// Factory for creating <see cref="IManifestLocator"/> instances.
/// </param>
/// <param name="artifactsProcessor">
/// The processor to use for plugin artifacts.
/// </param>
/// <param name="packageBuilder">
/// The builder to use for creating plugin packages.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public PackCommand(
IManifestLocatorFactory manifestLocatorFactory,
IPluginArtifactsProcessor artifactsProcessor,
IPackageBuilder packageBuilder,
ILogger<PackCommand> logger)
: base(manifestLocatorFactory, artifactsProcessor, logger)
{
this.packageBuilder = packageBuilder;
}
protected override int RunCore(PackArgs args, ProcessedPluginResult processedSource)
{
string? destFilePath = args.OutputFileFullPath;
if (destFilePath == null || !args.Overwrite)
{
string destFileName = destFilePath ??
Path.Combine(Environment.CurrentDirectory, $"{processedSource!.Metadata.Identity}{PackageConstants.PluginPackageExtension}");
destFilePath = Utils.GetAlterDestFilePath(destFileName);
}
string tmpFile = Path.GetTempFileName();
try
{
this.packageBuilder.Build(processedSource!, tmpFile);
File.Move(tmpFile, destFilePath, args.Overwrite);
}
catch (Exception ex)
{
this.logger.LogError($"Failed to create plugin package at {destFilePath} due to an exception {ex.Message}");
return 1;
}
finally
{
if (File.Exists(tmpFile))
{
File.Delete(tmpFile);
}
}
this.logger.LogInformation($"Successfully created plugin package at {destFilePath}");
return 0;
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Common;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Pack
{
internal class PackOptionsValidator
: PackGenCommonOptionsValidator<PackOptions, PackArgs>
{
public PackOptionsValidator(ILogger<PackOptionsValidator> logger)
: base(logger)
{
}
protected override bool TryValidateCore(PackOptions cliOptions, PackGenCommonArgs validatedCommonAppArgs, out PackArgs? validatedAppArgs)
{
validatedAppArgs = null;
if (cliOptions.OutputFilePath == null && cliOptions.Overwrite)
{
this.logger.LogError("Cannot overwrite output file when output file is not specified.");
return false;
}
string? outputFileFullPath = null;
if (cliOptions.OutputFilePath != null)
{
if (!Path.GetExtension(cliOptions.OutputFilePath).Equals(PackageConstants.PluginPackageExtension, StringComparison.OrdinalIgnoreCase))
{
this.logger.LogError($"Output file must have extension '{PackageConstants.PluginPackageExtension}'.");
return false;
}
try
{
outputFileFullPath = Path.GetFullPath(cliOptions.OutputFilePath);
}
catch (Exception ex)
{
this.logger.LogError($"Unable to get full path to output file: {ex.Message}");
return false;
}
string? outputDir = Path.GetDirectoryName(outputFileFullPath);
if (!Directory.Exists(outputDir))
{
this.logger.LogError($"The directory '{outputDir}' does not exist. Please provide a valid directory path or create the directory and try again.");
}
}
validatedAppArgs = new PackArgs(validatedCommonAppArgs, outputFileFullPath);
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using CommandLine;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Console
{
/// <summary>
/// A command line interface for the plugins toolkit commands using the Command Line Parser Library.
/// </summary>
internal class PluginsCli
{
private readonly ICommand<PackArgs> packCommand;
private readonly ICommand<MetadataGenArgs> metadataGenCommand;
private readonly IOptionsValidator<PackOptions, PackArgs> packOptionsValidator;
private readonly IOptionsValidator<MetadataGenOptions, MetadataGenArgs> metadataGenOptionsValidator;
private readonly ILogger<PluginsCli> logger;
/// <summary>
/// Creates a new instance of <see cref="PluginsCli"/>.
/// </summary>
/// <param name="packCommand">
/// The command to run when the pack verb.
/// </param>
/// <param name="metadataGenCommand">
/// The command to run when the metadata-gen verb.
/// </param>
/// <param name="packOptionsValidator">
/// The validator for the pack options.
/// </param>
/// <param name="metadataGenOptionsValidator">
/// The validator for the metadata-gen options.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public PluginsCli(
ICommand<PackArgs> packCommand,
ICommand<MetadataGenArgs> metadataGenCommand,
IOptionsValidator<PackOptions, PackArgs> packOptionsValidator,
IOptionsValidator<MetadataGenOptions, MetadataGenArgs> metadataGenOptionsValidator,
ILogger<PluginsCli> logger)
{
this.packCommand = packCommand;
this.metadataGenCommand = metadataGenCommand;
this.packOptionsValidator = packOptionsValidator;
this.metadataGenOptionsValidator = metadataGenOptionsValidator;
this.logger = logger;
}
/// <summary>
/// Parses and runs the command line arguments.
/// </summary>
/// <param name="args">
/// The command line arguments.
/// </param>
/// <returns>
/// 0 if the command was successful; otherwise, 1.
/// </returns>
public int Run(string[] args)
{
ParserResult<object> result = Parser.Default.ParseArguments<PackOptions, MetadataGenOptions>(args);
try
{
return result.MapResult(
(PackOptions opts) => RunPack(opts),
(MetadataGenOptions opts) => RunMetadataGen(opts),
errs => HandleParseError(errs));
}
catch (Exception ex)
{
this.logger.LogError("Unhandled exception occurred: {0}", ex.Message);
return 0;
}
}
private int RunPack(PackOptions packOptions)
{
if (!this.packOptionsValidator.TryValidate(packOptions, out PackArgs? packArgs))
{
this.logger.LogError("Failed to validate pack options.");
return 1;
}
return this.packCommand.Run(packArgs);
}
private int RunMetadataGen(MetadataGenOptions metadataGenOptions)
{
if (!this.metadataGenOptionsValidator.TryValidate(metadataGenOptions, out MetadataGenArgs? metadataGenArgs))
{
this.logger.LogError("Failed to validate metadata gen options.");
return 1;
}
return this.metadataGenCommand.Run(metadataGenArgs);
}
private int HandleParseError(IEnumerable<Error> errs)
{
string errors = string.Join(Environment.NewLine, errs.Select(x => x.ToString()));
this.logger.LogError($"Failed to parse command line arguments: \n{errors}");
return 1;
}
}
}

Просмотреть файл

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using CommandLine;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs
{
/// <summary>
/// Options for the metadata-gen verb.
/// </summary>
[Verb("metadata-gen", HelpText = $"Generates a {PackageConstants.PluginMetadataFileName} and a {PackageConstants.PluginContentsMetadataFileName} file from the specified plugin source directory.")]
internal class MetadataGenOptions
: PackGenCommonOptions
{
/// <summary>
/// Creates a new instance of <see cref="MetadataGenOptions"/>.
/// </summary>
/// <param name="sourceDirectory">
/// The directory containing the plugin binaries.
/// </param>
/// <param name="outputDirectory">
/// The directory where the metadata file(s) will be created.
/// </param>
/// <param name="overwrite">
/// Indicates that the destination file should be overwritten if it already exists.
/// </param>
/// <param name="manifestFilePath">
/// Path to the plugin manifest file.
/// </param>
public MetadataGenOptions(
string sourceDirectory,
string outputDirectory,
bool overwrite,
string manifestFilePath)
: base(sourceDirectory, overwrite, manifestFilePath)
{
this.OutputDirectory = outputDirectory;
}
/// <summary>
/// Gets the directory where the metadata file(s) will be created.
/// </summary>
[Option(
'o',
"output",
Required = false,
HelpText = "Directory where the metadata file(s) will be created. If not specified, the current directory will be used.")]
public string? OutputDirectory { get; }
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using CommandLine;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs
{
/// <summary>
/// Common options for the pack and metadata-gen verbs.
/// </summary>
internal abstract class PackGenCommonOptions
{
protected PackGenCommonOptions(
string sourceDirectory,
bool overwrite,
string? manifestFilePath)
{
this.SourceDirectory = sourceDirectory;
this.Overwrite = overwrite;
this.ManifestFilePath = manifestFilePath;
}
/// <summary>
/// Gets the directory containing the plugin binaries.
/// </summary>
[Option(
's',
"source",
Required = true,
HelpText = "The directory containing the plugin binaries.")]
public string SourceDirectory { get; }
/// <summary>
/// Gets whether the destination file should be overwritten if it already exists.
/// </summary>
[Option(
'w',
"overwrite",
Required = false,
HelpText = "Indicates that the destination file should be overwritten if it already exists.")]
public bool Overwrite { get; } = false;
/// <summary>
/// Gets the path to the plugin manifest file.
/// </summary>
[Option(
'm',
"manifest",
Required = false,
HelpText = $"Path to the plugin manifest file. If not specified, the tool searches the source directory for file named {Constants.BundledManifestName}")]
public string? ManifestFilePath { get; }
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using CommandLine;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs
{
/// <summary>
/// Options for the pack verb.
/// </summary>
[Verb("pack", HelpText = $"Creates a new {PackageConstants.PluginPackageExtension} package using specified metadata and source directory.")]
internal class PackOptions
: PackGenCommonOptions
{
/// <summary>
/// Creates a new instance of <see cref="PackOptions"/>.
/// </summary>
/// <param name="sourceDirectory">
/// The directory containing the plugin binaries.
/// </param>
/// <param name="outputFilePath">
/// The path to write the package file to.
/// </param>
/// <param name="overwrite">
/// Indicates that the destination file should be overwritten if it already exists.
/// </param>
/// <param name="manifestFilePath"></param>
public PackOptions(
string sourceDirectory,
string outputFilePath,
bool overwrite,
string manifestFilePath)
: base(sourceDirectory, overwrite, manifestFilePath)
{
this.OutputFilePath = outputFilePath;
}
/// <summary>
/// Gets the path to write the package file to.
/// </summary>
[Option(
'o',
"output",
Required = false,
HelpText = "The path to the output package file. If not specified, a file will be created in the current directory.")]
public string? OutputFilePath { get; }
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli
{
/// <summary>
/// Constants used by the CLI.
/// </summary>
public static class Constants
{
public const string ManifestSchemaFilePath = "Manifest/PluginManifestSchema.json";
public const string BundledManifestName = "pluginManifest.json";
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Performance.Toolkit.Plugins.Cli
{
/// <summary>
/// Validates the options provided by the command line interface.
/// </summary>
/// <typeparam name="TOptions">
/// The type of options to validate.
/// </typeparam>
/// <typeparam name="TArgs">
/// The type of arguments to pass to the application.
/// </typeparam>
internal interface IOptionsValidator<TOptions, TArgs>
{
/// <summary>
/// Validates the options provided by the command line interface and translates them into application arguments.
/// </summary>
/// <param name="cliOptions">
/// The options provided by the command line interface.
/// </param>
/// <param name="validatedAppArgs">
/// The application arguments, if the options are valid.
/// </param>
/// <returns>
/// <c>true</c> if the options are valid; <c>false</c> otherwise.
/// </returns>
bool TryValidate(TOptions cliOptions, [NotNullWhen(true)] out TArgs? validatedAppArgs);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Locates the manifest file specified on the command line.
/// </summary>
internal class CommandLineManifestFileLocator
: IManifestLocator
{
private readonly string manifestFilePath;
public CommandLineManifestFileLocator(string manifestFilePath)
{
this.manifestFilePath = manifestFilePath;
}
/// <inheritdoc />
public bool TryLocate([NotNullWhen(true)] out string? manifestFilePath)
{
manifestFilePath = this.manifestFilePath;
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json.Schema;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Loads the schema for the plugin manifest.
/// </summary>
internal interface IJsonSchemaLoader
{
/// <summary>
/// Loads the schema for the plugin manifest.
/// </summary>
/// <returns>
/// The json schema.
/// </returns>
JSchema LoadSchema();
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// A reader for plugin manifest files.
/// </summary>
internal interface IManifestFileReader
{
/// <summary>
/// Attempts to read the manifest file at the given path.
/// </summary>
/// <param name="manifestFilePath">
/// The path to the manifest file.
/// </param>
/// <param name="pluginManifest">
/// The parsed manifest file, if the manifest could be parsed; <c>null</c> otherwise.
/// </param>
/// <returns>
/// <c>true</c> if the manifest could be parsed; <c>false</c> otherwise.
/// </returns>
bool TryRead(string manifestFilePath, [NotNullWhen(true)] out PluginManifest? pluginManifest);
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Validates a plugin manifest file.
/// </summary>
internal interface IManifestFileValidator
{
/// <summary>
/// Validates the given manifest file.
/// </summary>
/// <param name="manifestFilePath">
/// The path to the manifest file to validate.
/// </param>
/// <param name="errorMessages">
/// Any error messages encountered during validation.
/// </param>
/// <returns>
/// <c>true</c> if the manifest file is valid; <c>false</c> otherwise.
/// </returns>
bool IsValid(string manifestFilePath, out List<string> errorMessages);
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Locates the manifest file.
/// </summary>
internal interface IManifestLocator
{
/// <summary>
/// Attempts to locate the manifest file.
/// </summary>
/// <param name="manifestFilePath">
/// The path to the manifest file, if found; <c>null</c> otherwise.
/// </param>
/// <returns>
/// <c>true</c> if the manifest file was found; <c>false</c> otherwise.
/// </returns>
bool TryLocate([NotNullWhen(true)] out string? manifestFilePath);
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Creates <see cref="IManifestLocator"/> instances.
/// </summary>
internal interface IManifestLocatorFactory
{
/// <summary>
/// Creates a <see cref="IManifestLocator"/> instance.
/// </summary>
/// <param name="args">
/// The <see cref="PackGenCommonArgs"/> to use when creating the <see cref="IManifestLocator"/>.
/// </param>
/// <returns>
/// A <see cref="IManifestLocator"/> instance.
/// </returns>
IManifestLocator Create(PackGenCommonArgs args);
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Schema;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Loads the manifest schema from a local file.
/// </summary>
internal sealed class LocalManifestSchemaLoader
: IJsonSchemaLoader
{
private readonly ILogger<LocalManifestSchemaLoader> logger;
private readonly Lazy<string> schemaFilePath;
/// <summary>
/// Initializes a new instance of the <see cref="LocalManifestSchemaLoader"/>
/// </summary>
/// <param name="logger">
/// Logger to use.
/// </param>
public LocalManifestSchemaLoader(ILogger<LocalManifestSchemaLoader> logger)
{
this.logger = logger;
this.schemaFilePath = new Lazy<string>(() => Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
Constants.ManifestSchemaFilePath));
}
/// <inheritdoc />
public JSchema LoadSchema()
{
string? fileText = File.ReadAllText(this.schemaFilePath.Value);
var schema = JSchema.Parse(fileText);
return schema;
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Validates a manifest file against the schema.
/// </summary>
internal sealed class ManifestJsonSchemaValidator
: IManifestFileValidator
{
private readonly IJsonSchemaLoader schemaLoader;
private readonly ILogger logger;
private readonly Lazy<JSchema> schema;
/// <summary>
/// Initializes a new instance of the <see cref="ManifestJsonSchemaValidator"/>
/// </summary>
/// <param name="schemaLoader">
/// The schema loader to use.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public ManifestJsonSchemaValidator(IJsonSchemaLoader schemaLoader, ILogger<ManifestJsonSchemaValidator> logger)
{
this.schemaLoader = schemaLoader;
this.schema = new Lazy<JSchema>(this.schemaLoader.LoadSchema);
this.logger = logger;
}
/// <inheritdoc />
public bool IsValid(string manifestPath, out List<string> errorMessages)
{
errorMessages = new List<string>();
string? fileText;
JToken? parsedManifest;
fileText = File.ReadAllText(manifestPath);
parsedManifest = JToken.Parse(fileText);
bool isValid = parsedManifest.IsValid(this.schema.Value, out IList<string> errors);
if (!isValid)
{
errorMessages.AddRange(errors);
}
return isValid;
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// A factory for creating <see cref="IManifestLocator"/> instances.
/// </summary>
internal class ManifestLocatorFactory
: IManifestLocatorFactory
{
private readonly ILoggerFactory loggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ManifestLocatorFactory"/>
/// </summary>
/// <param name="loggerFactory">
/// The logger factory to use.
/// </param>
public ManifestLocatorFactory(
ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}
/// <inheritdoc />
public IManifestLocator Create(PackGenCommonArgs args)
{
if (args.ManifestFileFullPath == null)
{
return new SourceDirectoryManifestLocator(args.SourceDirectoryFullPath, this.loggerFactory.CreateLogger<SourceDirectoryManifestLocator>());
}
return new CommandLineManifestFileLocator(args.ManifestFileFullPath);
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Core.Serialization;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// A reader for plugin manifests that reads from a file.
/// </summary>
internal sealed class ManifestReader
: IManifestFileReader
{
private readonly ISerializer<PluginManifest> serializer;
private readonly ILogger logger;
/// <summary>
/// Initializes a new instance of the <see cref="ManifestReader"/>
/// </summary>
/// <param name="serializer">
/// The serializer to use.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public ManifestReader(
ISerializer<PluginManifest> serializer,
ILogger<ManifestReader> logger)
{
this.serializer = serializer;
this.logger = logger;
}
/// <inheritdoc />
public bool TryRead(string manifestFilePath, [NotNullWhen(true)] out PluginManifest? pluginManifest)
{
pluginManifest = null;
try
{
using (FileStream manifestStream = File.OpenRead(manifestFilePath))
{
pluginManifest = this.serializer.Deserialize(manifestStream);
}
}
catch (IOException ex)
{
this.logger.LogError($"Failed to read {manifestFilePath} due to an IO exception {ex.Message}");
return false;
}
catch (JsonException ex)
{
this.logger.LogError($"Failed to deserialize {manifestFilePath} due to an JSON exception {ex.Message}.");
return false;
}
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Represents the identity of a plugin in a manifest.
/// </summary>
public sealed class PluginIdentityManifest
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginIdentityManifest"/>
/// </summary>
/// <param name="id">
/// The identifier of this plugin.
/// </param>
/// <param name="version">
/// The version of this plugin.
/// </param>
public PluginIdentityManifest(
string id,
Version version)
{
this.Id = id;
this.Version = version;
}
/// <summary>
/// Gets or sets the identifier of this plugin.
/// </summary>
public string Id { get; }
/// <summary>
/// Gets or sets the version of this plugin.
/// </summary>
public Version Version { get; }
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Represents the manifest for a plugin.
/// </summary>
public sealed class PluginManifest
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginManifest"/>s
/// </summary>
/// <param name="identity">
/// The identity of the plugin.
/// </param>
/// <param name="displayName">
/// The human-readable name of this plugin.
/// </param>
/// <param name="description">
/// The user friendly description of this plugin.
/// </param>
/// <param name="owners">
/// The owners of this plugin.
/// </param>
/// <param name="projectUrl">
/// The URL of the project that owns this plugin.
/// </param>
public PluginManifest(
PluginIdentityManifest identity,
string displayName,
string description,
IEnumerable<PluginOwnerInfoManifest> owners,
Uri projectUrl)
{
this.Identity = identity;
this.DisplayName = displayName;
this.Description = description;
this.Owners = owners;
this.ProjectUrl = projectUrl;
}
/// <summary>
/// Gets the identity of the plugin.
/// </summary>
public PluginIdentityManifest Identity { get; }
/// <summary>
/// Gets the human-readable name of this plugin.
/// </summary>
public string DisplayName { get; }
/// <summary>
/// Gets the user friendly description of this plugin.
/// </summary>
/// </summary>
public string Description { get; }
/// <summary>
/// Gets the owners of this plugin.
/// </summary>
public IEnumerable<PluginOwnerInfoManifest> Owners { get; }
/// <summary>
/// Gets the URL of the project that owns this plugin.
/// </summary>
public Uri ProjectUrl { get; }
}
}

Просмотреть файл

@ -0,0 +1,89 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "JSON Schema a plugin manifest",
"type": "object",
"required": [
"identity",
"displayName",
"description",
"projectUrl",
"owners",
"manifestVersion"
],
"properties": {
"identity": {
"type": "object",
"required": [
"id",
"version"
],
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for a plugin."
},
"version": {
"type": "string",
"pattern": "^(\\d+\\.\\d+\\.\\d+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$",
"description": "The version number of the plugin."
}
}
},
"description": {
"type": "string",
"description": "A short description of the plugin."
},
"displayName": {
"type": "string",
"description": "This shows up in tooltips for the 'name' property."
},
"projectUrl": {
"type": "string",
"format": "uri",
"description": "The URL of the project's home page."
},
"owners": {
"type": "array",
"description": "The owners of the plugin.",
"required": [
"name",
"address",
"emailAddresses",
"phoneNumbers"
],
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the owner."
},
"address": {
"type": "string",
"description": "The address of the owner."
},
"emailAddresses": {
"type": "array",
"items": {
"type": "string",
"format": "email",
"description": "The email address of the owner."
}
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "string",
"description": "The phone number of the owner."
}
}
}
}
},
"manifestVersion": {
"type": "number",
"description": "The version of the manifest schema.",
"const": 1.0
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Contains information about the plugin owner in the manifest.
/// </summary>
public sealed class PluginOwnerInfoManifest
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginOwnerInfoManifest"/>
/// </summary>
/// <param name="name">
/// The name of the plugin owner.
/// </param>
/// <param name="address">
/// The address of the owner, if any.
/// </param>
/// <param name="emailAddresses">
/// The email addresses of the owner, if any.
/// </param>
/// <param name="phoneNumbers">
/// The phone numbers of the owner, if any.
/// </param>
public PluginOwnerInfoManifest(
string name,
string address,
IEnumerable<string> emailAddresses,
IEnumerable<string> phoneNumbers)
{
this.Name = name;
this.Address = address;
this.EmailAddresses = emailAddresses;
this.PhoneNumbers = phoneNumbers;
}
/// <summary>
/// Gets the name of the plugin owner.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the address of the owner, if any.
/// </summary>
public string Address { get; }
/// <summary>
/// Gets the email addresses of the owner, if any.
/// </summary>
public IEnumerable<string> EmailAddresses { get; }
/// <summary>
/// Gets the phone numbers of the owner, if any.
/// </summary>
public IEnumerable<string> PhoneNumbers { get; }
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest
{
/// <summary>
/// Locates the manifest file in the source directory.
/// </summary>
internal class SourceDirectoryManifestLocator
: IManifestLocator
{
private readonly string sourceDir;
private readonly ILogger<SourceDirectoryManifestLocator> logger;
/// <summary>
/// Initializes a new instance of the <see cref="SourceDirectoryManifestLocator"/>
/// </summary>
/// <param name="sourceDirectory">
/// The source directory to search for the manifest file.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public SourceDirectoryManifestLocator(
string sourceDirectory,
ILogger<SourceDirectoryManifestLocator> logger)
{
this.sourceDir = sourceDirectory;
this.logger = logger;
}
/// <inheritdoc />
public bool TryLocate([NotNullWhen(true)] out string? manifestFilePath)
{
manifestFilePath = null;
var matchedFiles = Directory.EnumerateFiles(this.sourceDir, Constants.BundledManifestName, SearchOption.AllDirectories).ToList();
if (matchedFiles.Count == 0)
{
this.logger.LogError($"Directory does not contain {Constants.BundledManifestName} as expected: {this.sourceDir}.");
return false;
}
else if (matchedFiles.Count > 1)
{
this.logger.LogError($"Directory contains multiple manifests: {string.Join(", ", matchedFiles)}. Only one manifest is allowed.");
return false;
}
else
{
manifestFilePath = matchedFiles.First();
return true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>plugintool</ToolCommandName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Microsoft.Performance.SDK.Runtime.NetCoreApp\Microsoft.Performance.SDK.Runtime.NetCoreApp.csproj" />
<ProjectReference Include="..\..\Microsoft.Performance.Toolkit.Plugins.Core\Microsoft.Performance.Toolkit.Plugins.Core.csproj" />
<ProjectReference Include="..\..\Microsoft.Performance.Toolkit.Plugins.Runtime\Microsoft.Performance.Toolkit.Plugins.Runtime.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Manifest\PluginManifestSchema.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Packaging
{
/// <summary>
/// Defines the interface for a package builder.
/// </summary>
internal interface IPackageBuilder
{
/// <summary>
/// Builds a package from the given source path and writes it to the given target file path.
/// </summary>
/// <param name="sourcePath">
/// The source path to build from.
/// </param>
/// <param name="targetFilePath">
/// The target file path to write the package to.
/// </param>
void Build(ProcessedPluginResult sourcePath, string targetFilePath);
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.IO.Compression;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
using Microsoft.Performance.Toolkit.Plugins.Core.Metadata;
using Microsoft.Performance.Toolkit.Plugins.Core.Serialization;
using Microsoft.Performance.Toolkit.Plugins.Runtime.Package;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Packaging
{
/// <summary>
/// Creates a plugin package from a processed plugin directory using the zip format.
/// </summary>
internal sealed class ZipPluginPackageBuilder
: IPackageBuilder
{
private readonly ISerializer<PluginMetadata> metadataSerializer;
private readonly ISerializer<PluginContentsMetadata> contentsMetadataSerializer;
private readonly ILogger<ZipPluginPackageBuilder> logger;
/// <summary>
/// Initializes a new instance of the <see cref="ZipPluginPackageBuilder"/>.
/// </summary>
/// <param name="serializer">
/// The serializer to use to serialize the plugin metadata.
/// </param>
/// <param name="contentsMetadataSerializer">
/// The serializer to use to serialize the plugin contents metadata.
/// </param>
/// <param name="logger">
/// The logger to use.
/// </param>
public ZipPluginPackageBuilder(
ISerializer<PluginMetadata> serializer,
ISerializer<PluginContentsMetadata> contentsMetadataSerializer,
ILogger<ZipPluginPackageBuilder> logger)
{
this.metadataSerializer = serializer;
this.contentsMetadataSerializer = contentsMetadataSerializer;
this.logger = logger;
}
/// <inheritdoc/>
public void Build(ProcessedPluginResult processedDir, string destFilePath)
{
using var stream = new FileStream(destFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
using var zip = new ZipArchive(stream, ZipArchiveMode.Create, false);
foreach (string fileInfo in processedDir.ContentFiles)
{
string fileSourcePath = Path.Combine(processedDir.SourceDirectory, fileInfo);
string fileTargetPath = Path.Combine(PackageConstants.PluginContentFolderName, fileInfo);
zip.CreateEntryFromFile(fileSourcePath, fileTargetPath, CompressionLevel.Optimal);
}
ZipArchiveEntry metadataEntry = zip.CreateEntry(PackageConstants.PluginMetadataFileName);
using (Stream entryStream = metadataEntry.Open())
{
this.metadataSerializer.Serialize(entryStream, processedDir.Metadata);
}
ZipArchiveEntry contentsMetadataEntry = zip.CreateEntry(PackageConstants.PluginContentsMetadataFileName);
using (Stream entryStream = contentsMetadataEntry.Open())
{
this.contentsMetadataSerializer.Serialize(entryStream, processedDir.ContentsMetadata);
}
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Processing
{
/// <summary>
/// Represents a processor that can process a <see cref="PluginArtifacts"/> instance.
/// </summary>
internal interface IPluginArtifactsProcessor
{
/// <summary>
/// Attempts to process the given <see cref="PluginArtifacts"/> instance into a <see cref="ProcessedPluginResult"/>.
/// </summary>
/// <param name="artifacts">
/// The <see cref="PluginArtifacts"/> instance to process.
/// </param>
/// <param name="processedPlugin">
/// The created <see cref="ProcessedPluginResult"/> instance, if processing was successful.
/// </param>
/// <returns>
/// <c>true</c> if processing was successful; <c>false</c> otherwise.
/// </returns>
public bool TryProcess(PluginArtifacts artifacts, [NotNullWhen(true)] out ProcessedPluginResult? processedPlugin);
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Processing
{
/// <summary>
/// Represents the artifacts of a plugin.
/// </summary>
/// <param name="SourceDirectoryFullPath">
/// The full path to the directory containing the plugin binaries.
/// </param>
/// <param name="ManifestFileFullPath">
/// The full path to the manifest file.
internal record PluginArtifacts(
string SourceDirectoryFullPath,
string ManifestFileFullPath);
}

Просмотреть файл

@ -0,0 +1,285 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.SDK;
using Microsoft.Performance.SDK.Processing;
using Microsoft.Performance.SDK.Runtime;
using Microsoft.Performance.SDK.Runtime.NetCoreApp.Discovery;
using Microsoft.Performance.SDK.Runtime.NetCoreApp.Plugins;
using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest;
using Microsoft.Performance.Toolkit.Plugins.Core;
using Microsoft.Performance.Toolkit.Plugins.Core.Metadata;
using NuGet.Versioning;
using ProcessingSourceInfo = Microsoft.Performance.Toolkit.Plugins.Core.Metadata.ProcessingSourceInfo;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Processing
{
/// <summary>
/// Processes a plugin's artifacts.
/// </summary>
internal class PluginArtifactsProcessor
: IPluginArtifactsProcessor
{
private readonly IManifestFileValidator manifestValidator;
private readonly IManifestFileReader manifestReader;
private readonly ILogger<PluginArtifactsProcessor> logger;
private static readonly string? sdkAssemblyName = SdkAssembly.Assembly.GetName().Name;
public PluginArtifactsProcessor(
IManifestFileValidator manifestValidator,
IManifestFileReader manifestReader,
ILogger<PluginArtifactsProcessor> logger)
{
this.manifestValidator = manifestValidator;
this.manifestReader = manifestReader;
this.logger = logger;
}
/// <inheritdoc />
public bool TryProcess(PluginArtifacts artifacts, [NotNullWhen(true)] out ProcessedPluginResult? processedPlugin)
{
processedPlugin = null;
if (!TryProcessSourceDir(artifacts.SourceDirectoryFullPath, out ProcessedPluginSourceDirectory? processedDir))
{
this.logger.LogError($"Failed to process source directory {artifacts.SourceDirectoryFullPath}.");
return false;
}
string manifestFilePath = artifacts.ManifestFileFullPath;
try
{
if (!this.manifestValidator.IsValid(manifestFilePath, out List<string> validationMessages))
{
string errors = string.Join(Environment.NewLine, validationMessages);
this.logger.LogWarning($"Manifest file failed some json schema format validation checks: \n{errors}");
this.logger.LogWarning("Continuing with packing process but it is recommended to fix the validation errors and repack before publishing the plugin.");
}
}
catch (Exception ex)
{
this.logger.LogError($"Failed to validate manifest file {manifestFilePath} due to an exception {ex.Message}.");
return false;
}
if (!this.manifestReader.TryRead(manifestFilePath, out PluginManifest? manifest))
{
this.logger.LogError($"Failed to read manifest file {manifestFilePath}.");
return false;
}
var versionChecker = new TrackingVersionChecker();
using PluginsLoader pluginsLoader = new(
new IsolationAssemblyLoader(),
x => new SandboxPreloadValidator(x, versionChecker),
Logger.Create<PluginsLoader>());
bool loadSuccess = pluginsLoader.TryLoadPlugin(processedDir.FullPath, out ErrorInfo errorInfo);
if (!loadSuccess)
{
// TODO: Check error codes and throw more specific exceptions
this.logger.LogError($"Failed to load plugin from {processedDir.FullPath}: {errorInfo.Message}");
}
if (versionChecker.CheckedVersions.Count == 0)
{
this.logger.LogError($"Invalid plugin: {sdkAssemblyName} is not referenced anywhere in the plugin.");
return false;
}
if (versionChecker.CheckedVersions.Count > 1)
{
this.logger.LogError($"Mutiple versions of {sdkAssemblyName} are referenced in the plugin: " +
$"{string.Join(", ", versionChecker.CheckedVersions.Keys)}. Only one version is allowed.");
return false;
}
(SemanticVersion? pluginSDKversion, bool isVersionSupported) = versionChecker.CheckedVersions.Single();
if (!isVersionSupported)
{
SemanticVersion cliSDKVersion = SdkAssembly.Assembly.GetSemanticVersion();
if (pluginSDKversion.Major != cliSDKVersion.Major)
{
this.logger.LogError($"Plugin is built against SDK version {pluginSDKversion} but the sdk used in the CLI is {cliSDKVersion}. " +
"The major version of the SDK used in the CLI must match the major version of the SDK used to build the plugin. " +
"Please use the CLI that targets the same major version of the SDK as the plugin.");
}
if (pluginSDKversion.Minor > cliSDKVersion.Minor)
{
this.logger.LogError($"Plugin is built against SDK version {pluginSDKversion} but the sdk used in the CLI is {cliSDKVersion}. " +
"The minor version of the SDK used in the CLI must be greater than or equal to the minor version of the SDK used to build the plugin. " +
$"If your plugin does NOT use any features from SDK version {pluginSDKversion}, consider downgrading the plugin to use version {cliSDKVersion} or lower. " +
"Using the lowest minor version required will maximize compatibility of your plugin. " +
$"If your plugin does use features from SDK version {pluginSDKversion}, please update your CLI to a version that targets SDK version {pluginSDKversion} or later.");
}
}
if (!loadSuccess || !isVersionSupported)
{
return false;
}
PluginMetadata metadata = GenerateMetadata(processedDir, manifest, pluginSDKversion);
if (!TryGenerateContentsMetadata(pluginsLoader, out PluginContentsMetadata? contentsMetadata))
{
this.logger.LogError($"Failed to generate contents metadata for plugin.");
return false;
}
processedPlugin = new ProcessedPluginResult(artifacts.SourceDirectoryFullPath, processedDir.AllContentFilePaths, metadata, contentsMetadata!);
return true;
}
private bool TryProcessSourceDir(
string sourceDir,
[NotNullWhen(true)] out ProcessedPluginSourceDirectory? processedDir)
{
processedDir = null;
string? manifestFilePath = null;
long totalSize = 0;
int dllCount = 0;
var filesToPack = new List<string>();
foreach (string file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
{
string fileName = Path.GetFileName(file);
if (Path.GetExtension(file).Equals(".dll", StringComparison.OrdinalIgnoreCase))
{
dllCount++;
if (Path.GetFileNameWithoutExtension(file).Equals(sdkAssemblyName, StringComparison.OrdinalIgnoreCase))
{
this.logger.LogError($"{sdkAssemblyName} should not present in the directory.");
return false;
}
}
else if (fileName.Equals(Constants.BundledManifestName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
filesToPack.Add(Path.GetRelativePath(sourceDir, file));
totalSize += new FileInfo(file).Length;
}
if (dllCount == 0)
{
this.logger.LogError($"Directory does not contain any DLLs: {sourceDir}.");
return false;
}
processedDir = new ProcessedPluginSourceDirectory(
sourceDir,
filesToPack,
manifestFilePath,
totalSize);
return true;
}
private PluginMetadata GenerateMetadata(ProcessedPluginSourceDirectory processedDir, PluginManifest manifest, SemanticVersion sdkVersion)
{
this.logger.LogTrace($"Generating metadata for plugin {manifest.Identity.Id}-{manifest.Identity.Version}");
PluginIdentity identity = new(manifest.Identity.Id, manifest.Identity.Version);
ulong installedSize = (ulong)processedDir.PluginSize;
IEnumerable<PluginOwnerInfo> owners = manifest.Owners.Select(
o => new PluginOwnerInfo(o.Name, o.Address, o.EmailAddresses.ToArray(), o.PhoneNumbers.ToArray()));
Version convertedSDKVersion = new(sdkVersion.Major, sdkVersion.Minor, sdkVersion.Patch);
PluginMetadata metadata = new(
identity,
installedSize,
manifest.DisplayName,
manifest.Description,
convertedSDKVersion,
manifest.ProjectUrl, owners);
return metadata;
}
private bool TryGenerateContentsMetadata(PluginsLoader pluginsLoader, out PluginContentsMetadata? contentsMetadata)
{
var processingSourcesMetadata = pluginsLoader.LoadedProcessingSources.Select(x => CreateProcessingSourceMetadata(x)).ToList();
// TODO: #294 Figure out how to extract description of a datacooker.
var dataCookers = pluginsLoader.Extensions.SourceDataCookers
.Concat(pluginsLoader.Extensions.CompositeDataCookers)
.Select(x => new DataCookerMetadata(x.DataCookerId, null, x.SourceParserId))
.ToList();
var tables = pluginsLoader.Extensions.TablesById.Values
.Select(x => new TableMetadata(
x.TableDescriptor.Guid,
x.TableDescriptor.Name,
x.TableDescriptor.Description,
x.TableDescriptor.Category,
x.TableDescriptor.IsMetadataTable))
.ToList();
contentsMetadata = new(processingSourcesMetadata, dataCookers, tables);
return true;
}
private static ProcessingSourceMetadata CreateProcessingSourceMetadata(ProcessingSourceReference psr)
{
// Tables
IEnumerable<TableMetadata> dataTables = psr.Instance.DataTables.Select(x => new TableMetadata(
x.Guid,
x.Name,
x.Description,
x.Category,
false));
IEnumerable<TableMetadata> metadataTables = psr.Instance.MetadataTables.Select(x => new TableMetadata(
x.Guid,
x.Name,
x.Description,
x.Category,
true));
// Data Sources
var dataSourcesMetadata = new List<DataSourceMetadata>();
foreach (DataSourceAttribute? ds in psr.DataSources)
{
if (ds is FileDataSourceAttribute fds)
{
dataSourcesMetadata.Add(new DataSourceMetadata(fds.FileExtension, fds.Description));
}
else if (ds is DirectoryDataSourceAttribute)
{
dataSourcesMetadata.Add(new DataSourceMetadata(DataSourceNameConstants.DirectoryDataSourceName, ds.Description));
}
else if (ds is ExtensionlessFileDataSourceAttribute)
{
dataSourcesMetadata.Add(new DataSourceMetadata(DataSourceNameConstants.ExtensionlessDataSourceName, ds.Description));
}
else
{
Debug.Assert(false, $"Unknown DataSourceAttribute type: {ds.GetType()}");
}
}
ProcessingSourceMetadata metadata = new(
version: Version.Parse(psr.Version),
name: psr.Name,
description: psr.Description,
guid: psr.Instance.TryGetGuid(),
aboutInfo: new ProcessingSourceInfo(psr.Instance.GetAboutInfo()),
availableTables: dataTables.Concat(metadataTables),
supportedDataSources: dataSourcesMetadata);
return metadata;
}
private record ProcessedPluginSourceDirectory(
string FullPath,
IReadOnlyList<string> AllContentFilePaths,
string? ManifestFilePath,
long PluginSize);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Performance.Toolkit.Plugins.Core.Metadata;
namespace Microsoft.Performance.Toolkit.Plugins.Cli.Processing
{
/// <summary>
/// Represents the result of processing plugin artifacts.
/// </summary>
/// <param name="SourceDirectory">
/// The path to the directory containing the plugin binaries.
/// </param>
/// <param name="ContentFiles">
/// The relative paths of the plugin content files to the source directory.
/// </param>
/// <param name="Metadata">
/// The generated plugin metadata.
/// </param>
/// <param name="ContentsMetadata">
/// The generated plugin contents metadata.
/// </param>
internal record ProcessedPluginResult(
string SourceDirectory,
IReadOnlyList<string> ContentFiles,
PluginMetadata Metadata,
PluginContentsMetadata ContentsMetadata);
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.MetadataGen;
using Microsoft.Performance.Toolkit.Plugins.Cli.Commands.Pack;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console;
using Microsoft.Performance.Toolkit.Plugins.Cli.Console.Verbs;
using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest;
using Microsoft.Performance.Toolkit.Plugins.Cli.Packaging;
using Microsoft.Performance.Toolkit.Plugins.Cli.Processing;
using Microsoft.Performance.Toolkit.Plugins.Core.Metadata;
using Microsoft.Performance.Toolkit.Plugins.Core.Serialization;
namespace Microsoft.Performance.Toolkit.Plugins.Cli
{
/// <summary>
/// Represents the entry point for the CLI.
/// </summary>
public sealed class Program
{
/// <summary>
/// Main entry point.
/// </summary>
/// <param name="args">
/// The command line arguments.
/// </param>
/// <returns>
/// A task whose result is the exit code. 0 on success; otherwise, non-zero.
/// </returns>
public static int Main(string[] args)
{
return CreatPluginsCli().Run(args);
}
private static PluginsCli CreatPluginsCli()
{
ServiceProvider serviceProvider = new ServiceCollection()
.AddLogging(x => x.AddConsole())
.AddSingleton<PluginsCli>()
.AddSingleton<ICommand<PackArgs>, PackCommand>()
.AddSingleton<ICommand<MetadataGenArgs>, MetadataGenCommand>()
.AddSingleton<IOptionsValidator<PackOptions, PackArgs>, PackOptionsValidator>()
.AddSingleton<IOptionsValidator<MetadataGenOptions, MetadataGenArgs>, MetadataGenOptionsValidator>()
.AddSingleton<IPluginArtifactsProcessor, PluginArtifactsProcessor>()
.AddSingleton<IPackageBuilder, ZipPluginPackageBuilder>()
.AddSingleton<ISerializer<PluginManifest>>(SerializationUtils.GetJsonSerializer<PluginManifest>(new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}))
.AddSingleton<ISerializer<PluginMetadata>>(SerializationUtils.GetJsonSerializerWithDefaultOptions<PluginMetadata>())
.AddSingleton<ISerializer<PluginContentsMetadata>>(SerializationUtils.GetJsonSerializerWithDefaultOptions<PluginContentsMetadata>())
.AddSingleton<IManifestFileReader, ManifestReader>()
.AddSingleton<IManifestFileValidator, ManifestJsonSchemaValidator>()
.AddSingleton<IJsonSchemaLoader, LocalManifestSchemaLoader>()
.AddSingleton<IManifestLocatorFactory, ManifestLocatorFactory>()
.BuildServiceProvider();
return serviceProvider.GetRequiredService<PluginsCli>();
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Performance.Toolkit.Plugins.Cli
{
/// <summary>
/// Contains utility methods.
/// </summary>
public static class Utils
{
/// <summary>
/// Gets an alternate file path for the given file path if the given file path already exists in the file system.
/// </summary>
/// <param name="fullfilePath">
/// The full file path to get an alternate file path for.
/// </param>
/// <returns>
/// An alternate file path for the given file path or the given file path if it does not already exist in the file system.
/// </returns>
public static string GetAlterDestFilePath(string fullfilePath)
{
string? directory = Path.GetDirectoryName(fullfilePath);
string name = Path.GetFileNameWithoutExtension(fullfilePath);
string extension = Path.GetExtension(fullfilePath);
string alterFileName = fullfilePath;
int fileCount = 1;
while (File.Exists(alterFileName))
{
alterFileName = Path.Combine(directory!, $"{name}_({fileCount++}){extension}");
}
return alterFileName;
}
}
}

Просмотреть файл

@ -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
}
}
}

Просмотреть файл

@ -12,7 +12,8 @@
"../",
"!../SDK.sln",
"!Microsoft.Performance.Toolkit.Plugins.Runtime.Tests",
"!Microsoft.Performance.Toolkit.Plugins.Core.Tests"
"!Microsoft.Performance.Toolkit.Plugins.Core.Tests",
"!Tools/"
],
"cloudBuild": {
"buildNumber": {

Просмотреть файл

@ -23,8 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.Toolk
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{AB9CEC9E-61E6-4FDC-912B-6848C3CD4066}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginConfigurationEditor", "Tools\PlugInConfigurationEditor\PluginConfigurationEditor.csproj", "{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.SDK.Runtime.NetCoreApp.Tests", "Microsoft.Performance.SDK.Runtime.NetCoreApp.Tests\Microsoft.Performance.SDK.Runtime.NetCoreApp.Tests.csproj", "{A0488905-9AA2-4CCB-BCB3-B758FF0E564A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.Toolkit.Engine.Tests.Driver", "Microsoft.Performance.Toolkit.Engine.Tests.Driver\Microsoft.Performance.Toolkit.Engine.Tests.Driver.csproj", "{D8CACB6A-C746-4DB6-BC16-B32B2149CEED}"
@ -39,6 +37,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.Toolk
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.Toolkit.Plugins.Runtime.Tests", "PluginsSystem\Microsoft.Performance.Toolkit.Plugins.Runtime.Tests\Microsoft.Performance.Toolkit.Plugins.Runtime.Tests.csproj", "{DECEF627-35FD-44B8-AB54-678C09CEFC9D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{86E14EA1-084E-4C21-AA14-AAAC83992D86}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Performance.Toolkit.Plugins.Cli", "PluginsSystem\Tools\Microsoft.Performance.Toolkit.Plugins.Cli\Microsoft.Performance.Toolkit.Plugins.Cli.csproj", "{487C7320-8CCF-45BC-9622-1B55B5194259}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginConfigurationEditor", "Tools\PluginConfigurationEditor\PluginConfigurationEditor.csproj", "{B010E944-C96D-4152-8CA6-38E5D53070E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -157,18 +161,6 @@ Global
{18FAE0A5-A5B4-423F-9D53-4088907714BA}.Release|x64.Build.0 = Release|Any CPU
{18FAE0A5-A5B4-423F-9D53-4088907714BA}.Release|x86.ActiveCfg = Release|Any CPU
{18FAE0A5-A5B4-423F-9D53-4088907714BA}.Release|x86.Build.0 = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|x64.ActiveCfg = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|x64.Build.0 = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|x86.ActiveCfg = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Debug|x86.Build.0 = Debug|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|Any CPU.Build.0 = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|x64.ActiveCfg = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|x64.Build.0 = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|x86.ActiveCfg = Release|Any CPU
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3}.Release|x86.Build.0 = Release|Any CPU
{A0488905-9AA2-4CCB-BCB3-B758FF0E564A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0488905-9AA2-4CCB-BCB3-B758FF0E564A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0488905-9AA2-4CCB-BCB3-B758FF0E564A}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -241,16 +233,42 @@ Global
{DECEF627-35FD-44B8-AB54-678C09CEFC9D}.Release|x64.Build.0 = Release|Any CPU
{DECEF627-35FD-44B8-AB54-678C09CEFC9D}.Release|x86.ActiveCfg = Release|Any CPU
{DECEF627-35FD-44B8-AB54-678C09CEFC9D}.Release|x86.Build.0 = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|Any CPU.Build.0 = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|x64.ActiveCfg = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|x64.Build.0 = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|x86.ActiveCfg = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Debug|x86.Build.0 = Debug|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|Any CPU.ActiveCfg = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|Any CPU.Build.0 = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|x64.ActiveCfg = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|x64.Build.0 = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|x86.ActiveCfg = Release|Any CPU
{487C7320-8CCF-45BC-9622-1B55B5194259}.Release|x86.Build.0 = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|x64.Build.0 = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Debug|x86.Build.0 = Debug|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|Any CPU.Build.0 = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x64.ActiveCfg = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x64.Build.0 = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x86.ActiveCfg = Release|Any CPU
{B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57B0088B-7A62-480A-A6CF-2F1BFA22FBC3} = {AB9CEC9E-61E6-4FDC-912B-6848C3CD4066}
{102CE852-11E9-4A4F-8E8A-074258CCBEB2} = {F5125921-DE2A-43BC-89C6-8584E2F23867}
{7FCE8CC1-055F-42AA-ACCD-369D41D0B824} = {F5125921-DE2A-43BC-89C6-8584E2F23867}
{FE2BA03D-A51C-4991-8041-966D9D368FE3} = {F5125921-DE2A-43BC-89C6-8584E2F23867}
{DECEF627-35FD-44B8-AB54-678C09CEFC9D} = {F5125921-DE2A-43BC-89C6-8584E2F23867}
{86E14EA1-084E-4C21-AA14-AAAC83992D86} = {F5125921-DE2A-43BC-89C6-8584E2F23867}
{487C7320-8CCF-45BC-9622-1B55B5194259} = {86E14EA1-084E-4C21-AA14-AAAC83992D86}
{B010E944-C96D-4152-8CA6-38E5D53070E1} = {AB9CEC9E-61E6-4FDC-912B-6848C3CD4066}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C9B73E61-5E48-45BD-B144-AFEFF5E44A52}