Add Plugin CLI Tool (#317)
* 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:
Родитель
6330c1c14f
Коммит
ab49926ec5
|
@ -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": {
|
||||
|
|
48
src/SDK.sln
48
src/SDK.sln
|
@ -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}
|
||||
|
|
Загрузка…
Ссылка в новой задаче