From 5059ea28216309b1b1d7b4999ec992f83467f4c1 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 1 Feb 2018 17:59:51 -0800 Subject: [PATCH 1/9] Finish RFC for supporting experimental features --- .../RFCNNNN-Support-Experimental-Features.md | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md new file mode 100644 index 00000000..301cb845 --- /dev/null +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -0,0 +1,451 @@ +--- +RFC: RFC +Author: Dongbo Wang, Dan Travison +Status: Draft +SupercededBy: N/A +Version: 1.0 +Area: Engine and Configuration +Comments Due: March 2nd, 2018 +Plan to implement: Yes +--- + +# Support Experimental Features via Configuration + +This RFC is about adding the Experimental Feature support in PowerShell through the PowerShell configuration. + +The Experimental Feature support in PowerShell is to provide a mechanism for experimental features to coexist +with exsiting stable features in PowerShell or PowerShell modules. +An experimental feature would not be enabled +unless a user opt in by properly configuring the `powershell.config.json` file. +Experimental features should not affect any stable features +and not present any side effects +when they are all disabled. + +## Motivation + +This is a highly demanded feature which we believe will vastly improve the productivity of +PowerShell developers on experimenting or iterating new features in PowerShell and PowerShell modules. + +The Experimental Feature support would make it easy for users to try out features +that are in early or intermediate stages of development, +and thus, allow the developers to get early feedback +and make adjustments to the design or implementation based on the feedback. + +For users that do not care about the new features, +they can continue to enjoy the bug fixes and other improvements from the new releases +without worrying about regressions, issues, or side effects caused by the new features. + +For example, PowerShell team plans to rewrite the `FileSystemProvider` in future +to reduce the code complexity and improve the performance. +During the development, +the `FileSystemProvider vnext` can coexist with the existing one +and be exposed to the user as an experimental feature, +so that PowerShell team can get early feedback and issue reports. +At the same time, users can continue to use the existing `FileSystemProvider` +without being affected by the known or unknown issues introduced by the `FileSystemProvider vnext`. + +## Specification + +### Experimental Feateure Names + +Experimental features for PowerShell components and built-in modules should be named as `PSXXX`, +where `XXX` is the real feature name, which is a single word in camel case. +For example: + +```none +PSDomainSpecificLanguage +PSFileSystemProviderV2 +``` + +Experimental features for a third-party PowerShell module should be named with +the module name as the fully qualified namespace to reduce the chance of collision. +The recommended naming conversion is to use the module name followed by a single-word feature name, all in camel case. +For example: + +```none +ModuleName.FeatureName +``` + +### Enable Experimental Features + +An experimental feature is enabled by adding the feature name to the experimental feature list +in the `powershell.config.json` file under `$PSHOME`. +The `JSON` schema should look like this: + +```json +{ + "ExperimentalFeatures": [ + "A-Feature-name", + "Another-Feature-Name" + ] +} +``` + +PowerShell reads the experimental feature list from the configuration file only once when starting up. + +- For `pwsh`, the list will be read in `ConsoleHost` as soon as possible, + so that experimental features targeting the `ConsoleHost` can be turned on early enough. +- When creating a Runspace, PowerShell will check if the list has been read, + and read the list if not yet. + This is to make sure experimental features can be enabled when an application is hosting PowerShell. + +Feature names from the list will be populated to a read-only immutable HashSet that is static to the whole process. +The HashSet will be exposed to users via an API as well as an automatic variable. +The API makes it easy to query for enabled features in C# code, +while the automatic variable makes it easy to query from PowerShell scripts. + +### Experimental Feature Implementation Scenarios + +#### Code Path Divided + +The first implementation scenario of an experimental feature is to divide the code path based on whether a feature is enabled. +This probably would be the most common pattern for implementing any experimental fatures. +PowerShell exposes the enabled experimental feature names through both an API and an automatic variable, +so it could be very easy for both C# code and PowerShell script to access this information. + +#### Cmdlet and Parameter + +The second implementation scenario is manipulate cmdlets and parameters. +There are three categories that we will discuss as follows. + +##### Add New Cmdlets + +There are two cases in this scenario: + +- A new cmdlet is added by the experimental feature. + The new cmdlet should be exposed when the experimental feature is enabled. +- A new cmdlet is added by the experimental feature to overide an existing cmdlet with the same name. + When the experimental feature is enabled, + the existing cmdlet should be hidden and the new cmdlet should be exposed. + +An example for the first case: +> A new cmdlet `Enable-SSHRemoting` is added as part of an experimental feature about Remoting over SSH. +We want to expose this cmdlet only if this experimental feature is enabled. + +An example for the second case: +> We want to rewrite the `Invoke-WebRequest` cmdlet completely from scratch. +We don't want to make changes directly to the existing code due to the risk of regression to the existing cmdelt. +Instead, we prefer to write new code from scratch in a new C# type +and replace the existing `Invoke-WebRequest` when this experimental feature is enabled. + +The proposal to this scenario is to add a new attribute `[Experimental()]` that can apply to a cmdlet. +The attribute takes two arguments: `FeatureName` and `FeatureAction`. + +- `FeatureName` is a string, indicating the experimental feature the attribute is associated with. +- `FeatureAction` is an enum with two members (shown as follows), + and it indicates the action to take on the cmdlet that has the attribute declared. + +```c# +public enum FeatureAction +{ + Hide, + Show +} +``` + +> Context: Cmdlet Registration -- The process that PowerShell reads assemblies or scripts +and create cmdlets for the cmdlet/function implementations defined in those sources. + +During the Cmdlet Registration, PowerShell can decide whether to process or ignore a cmdlet with the following workflow: + +- Check if the `[Experimental()]` attribute is declared + - if so, check if the associated experimental feature is enabled + - if so, ignore the cmdlet if the action to take is `Hide`; + process the cmdlet if the action to take is `Show`. + - if not, ignore the cmdlet if the action to take is `Show`; + process the cmdlet if the action to take is `Hide` + - if not, process the cmdlet as is today + +The following code snippets are examples of using this attribute for cmdlet/function: + +```c# +[Experimental("PSWebCmdletV2", FeatureAction.Show)] +[Cmdlet(Verbs.Invoke, "WebRequest")] +public class InvokeWebRequsetCommandV2 : WebCmdletBaseV2 { ... } + +[Experimental("PSWebCmdletV2", FeatureAction.Hide)] +[Cmdlet(Verbs.Invoke, "WebRequest")] +public class InvokeWebRequestCommand : WebCmdletBase { ... } +``` + +```powershell +function Enable-SSHRemoting { + [Experimental("PSSSHRemoting", "Show")] + [CmdletBinding()] + param() + ... +} +``` + +##### Add New Parameters + +There are two cases in this scenario: + +- A new parameter is added to a cmdlet by the experimental feature. + The new cmdlet parameter should be exposed when the experimental feature is enabled. +- An existing parameter is removed from a cmdlet by the experimental feature. + The existing cmdlet parameter should be hidden when the experimental feature is enabled. + +An example for the first case +> We want to add a new parameter to `Add-Type` for a new feature for compiling C# code. +We want to expose this parameter only if the experimental feature is enabled. + +An example for the second case: +> We want to remove a problematic parameter from `Add-Type`. +In order to evaluate the impact of the breaking chagne, +we want to hide it only if the experimental feature is enabled. + +The same `[Experimental()]` attribute can also be used in this scenario. +During the Cmdlet Registration, when processing parameters for a cmdlet/function, +PowerShell can decide whether to process or ignore a given parameter with the following workflow: + +- Check if the `[Experimental()]` attribute is declared + - if so, check if the associated experimental feature is enabled + - if so, ignore the parameter if the action to take is `Hide`; + process the parameter if the action to take is `Show`. + - if not, ignore the parameter if the action to take is `Show`; + process the parameter if the action to take is `Hide` + - if not, process the parameter as is today + +The following code snippets are examples of using this attribute for cmdlet/function: + +```c# +[Experimental("PSNewAddTypeCompilation", FeatureAction.Show)] +[Parameter(ParameterSet = "NewCompilation")] +public CompilationParameters CompileParameters { ... } + +[Experimental("PSNewAddTypeCompilation", FeatureAction.Hide)] +[Parameter()] +public CodeDom CodeDom { ... } +``` + +```powershell +param( + [Experimental("PSNewFeature", "Show")] + [string] $NewName, + + [Experimental("PSNewFeature", "Hide")] + [string] $OldName +) +``` + +##### Control Parameter Sets + +There are two cases in this scenario: + +- Add an existing parameter to a parameter set. + The parameter set could be an existing one, or a new one created for the experimental feature. + The parameter should be in the parameter set if the experimental feature is enabled. +- Remove an existing parameter from a parameter set. + The parameter should not be in the parameter set if the experimental feature is enabled. + +An example for the first case: +> We want to support a new transport layer for `Invoke-Command`, +which will introduce a new parameter set. +Parameters `-ScirptBlock` and `-FilePath` need to be added to the new parameter set +when the experimental feature is enabled. + +An example for the second case: +> We want to try out a breaking change that involves removing a parameter set. +For the parameters that are in the parameter set, +they should be removed from the parameter set when the experimental feature is enabled. + +The proposal to this scenario is to add two properties `FeatureName` and `FeatureAction` to the `[Parameter()]` attribute. +So a `Parameter` attribute can be associated with an experimental feature. +When the experimental feature is enabled, +PowerShell can choose to process or ignore the `Parameter` attribute depending on the `FeatureAction`. + +The following code snippets are examples of using the new properties in a `Parameter` attribute: + +```c# +[Parameter(Position = 1, Mandatory = true, + ParameterSetName = "SetAboutToAdd", + FeatureName = "PSAddTheSet", + FeatureAction = FeatureAction.Show)] +[Parameter(Position = 1, Mandatory = true, + ParameterSetName = "SetAboutToRemove", + FeatureName = "PSRemoveTheSet", + FeatureAction = FeatureAction.Hide)] +public ScriptBlock ScriptBlock { ... } +``` + +```powershell +param( + [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToAdd", + FeatureName = "PSAddTheSet", FeatureAction = "Show")] + [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToRemove", + FeatureName = "SetAboutToRemove", FeatureAction = "Hide")] + [string] $Name, +) +``` + +#### PowerShell Class and Members + +PowerShell Class is generated during the script compilation, +so runtime checks have no control over it. +If an experimental feature of a module involves new declaration of PowerShell Class, +the PowerShell Class will be exposed through `using Module ` +even if the experimental feature is not enabled. + +The proposal to this scenario is to also apply the `[Experimental()]` attribute +to PowerShell Class and its members. +When processing `TypeDefinitionAst`, PowerShell can filter out a `TypeDefinitionAst` node +depending on whether the experimental feature is enabled and the `FeatureAction` value. +The filtering will need to be done in the following places (could be more): + +- [`SymbolTable.AddTypesInScope(Ast ast)`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/SymbolResolver.cs#L168) +- [`Compiler.GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List exprs)`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/Compiler.cs#L2199) +- [`PSModuleInfo.CreateExportedTypeDefinitions(ScriptBlockAst moduleContentScriptBlockAsts)`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs#L612) +- [`CompletionCompleters.CompleteType(CompletionContext context, string prefix = "", string suffix = "")`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs#L5844) + +The searching for `TypeDefinitionAst` nodes should be done in one method, +so that there is no duplication of the filtering logic for the `[Experimental()]` attribute. + +As for the type members with the `[Experimental()]` attributes, +[`TypeDefiner`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/PSType.cs#L17) needs to be updated to process or skip a type member when processing a `TypeDefinitionAst` node, +depending on whether the experimental feature is enabled and the `FeatureAction` value. + +#### Exposed Public APIs + +An experimental feature may expose public C# APIs. +Public C# APIs cannot be hidden. +Even if we alter the `DotNetAdapter` to filter out those public APIs from the experimental feature in PowerShell, +they will still be visible to applications that host PowerShell. +Therefore, we will not do anything about the public APIs exposed from experimental features. + +### Experimental Feature Discovery + +A user should be able to discover the available experimental features in PowerShell, +including the ones in the PowerShell engine as well as the ones from modules. + +#### PowerShell Engine Experimental Feature + +For experimental features in PowerShell engine, +an internal enum `EngineExperimentalFeatures` will be used to track the names of all available engine experimental features. +When adding a new experimental feature, its name needs to be added to the enum for the feature to be discoverable to users. +The feature description needs to be added as a resource string, +and the id of the resource string needs to be constructible from the feature name, +such as `XXX-Description` where `XXX` is the feature name. +Here is an example of the enum and a description resource string. + +```c# +internal enum EngineExperimentalFeatures +{ + None, + PSDomainSpecificLanguage, + PSFileSystemProviderV2 +} +``` +```xml +// EngineExperimentalFeature.resx + + Rewrite file system provider for better performance. + +``` + +#### Module Experimental Feature + +For experimental features in modules, +metadata about an experimental feature should be kept in the module manifest. +We use the `PrivateData` section of a module manifest to expose the experimental features from the module. +Here is an example: + +```powershell +PrivateData = @{ + ExperimentalFeature = @{ + Name = "PSWebCmdletV2" + Description = "Rewrite the web cmdlets for better performance" + } +} +``` + +PowerShell module components, such as `Import-Module` and module analysis, +will be updated to incorporate this metadata to the resulted `PSModuleInfo` object. + +#### Get-ExperimentalFeature + +A new cmdlet `Get-ExperimentalFeature` will be added to return all available experimental features of a PowerShell session. +The returned experimental features are represented by the type `ExperimentalFeature`, +whose definition looks as follows: + +```c# +public class ExperimentalFeature +{ + // Feature name + public string Name { get; } + // Feature description + public string Description { get; } + // Feature source (e.g. module name) + public string Source { get; } +} +``` + +The cmdlet first goes through the enum members of `EngineExperimentalFeatures` to find all engine experimental features. +For each of them, the description resource string will be retrieved and the `Source` will be set as `PSEngine`. + +Then the cmdlet goes through all loaded modules and get the available experimental features from each module. +For each of them, the `Name` and `Description` are from the metadata. The `Source` will be the module name. + +Lastly, the cmdlet can go through not-yet-loaded modules that are in the module path, +in a similar way as `Get-Command` does, +with the help of the module analysis code. + +### Experimental Feature Dependencies + +> NOTE: This section may not be in scope for the first iteration of our implementation. + +An experimental feature in a module can depend on another experimental feature. +The latter could be a PowerShell engine feature, +or a feature from the same module, +or one from another module. +Given an experimental feature name, +it's easy to tell whether it's a PowerShell engine feature +or a feature from a module, +and in the latter case, the module name. + +When loading a module, if an experimental feature of the module is enabled, +PowerShell needs to check whether the dependencies are satisfied, +in a way that's similar to how PowerShell processes the nested modules. +Here is the high-level description of the scenarios: + +- If the enabled experimental feature depends on a PowerShell engine experimental feature + that is not enabled, then fail the loading with the appropriate error message. +- If the enabled experimental feature depends on an experimental feature from the same module + that is not enabled, then fail the loading with the appropriate error message. +- If the enabled experimental feature depends on an experimental feature from another module, + then the loading would fail unless + - the latter module is specified in the `NestedModules` + - the latter experimental feature is enabled + +In order to represent the dependencies, +the metadata of an experimental feature in `.psd1` needs to add a `Dependency` field as follows: + +```powershell +PrivateData = @{ + ExperimentalFeature = @{ + Name = "PSWebCmdletV2" + Description = "Rewrite the web cmdlets for better performance" + Dependency = "", "..." + } +} +``` + +correspondingly, a new property `Dependency` needs to be added to the type `ExperimentalFeature`: + +```c# +public class ExperimentalFeature +{ + // Feature name + public string Name { get; } + // Feature description + public string Description { get; } + // Feature source (e.g. module name) + public string Source { get; } + // Features it depends on + public ExperimentalFeature[] Dependency { get; } +} +``` + +## Alternate Proposals and Considerations + +No alternate proposals. From fd8be53a59453477a06892eedff67176ea0ee2e3 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 5 Feb 2018 10:02:45 -0800 Subject: [PATCH 2/9] Minor update --- .../RFCNNNN-Support-Experimental-Features.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index 301cb845..4ca5ba3e 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -177,6 +177,9 @@ function Enable-SSHRemoting { } ``` +> Implementation Note: the attribute `[Experimental()]` could be applied to a script block directly. +Execution of such a script block should error out when the associated experimental feature is not enabled. + ##### Add New Parameters There are two cases in this scenario: @@ -446,6 +449,13 @@ public class ExperimentalFeature } ``` +### Test Experimental Features + +How to test the combination of various experimental features would be a hard problem to solve, +as well as a problem that we have to solve before using the experimental feature support. + +> Not sure how to handle it yet ... + ## Alternate Proposals and Considerations No alternate proposals. From ab3ee1574029da085f8f99a91f9fd82ef70b91ec Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 5 Feb 2018 14:30:23 -0800 Subject: [PATCH 3/9] Fix typos --- .../RFCNNNN-Support-Experimental-Features.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index 4ca5ba3e..a7a0d516 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -14,7 +14,7 @@ Plan to implement: Yes This RFC is about adding the Experimental Feature support in PowerShell through the PowerShell configuration. The Experimental Feature support in PowerShell is to provide a mechanism for experimental features to coexist -with exsiting stable features in PowerShell or PowerShell modules. +with existing stable features in PowerShell or PowerShell modules. An experimental feature would not be enabled unless a user opt in by properly configuring the `powershell.config.json` file. Experimental features should not affect any stable features @@ -38,15 +38,15 @@ without worrying about regressions, issues, or side effects caused by the new fe For example, PowerShell team plans to rewrite the `FileSystemProvider` in future to reduce the code complexity and improve the performance. During the development, -the `FileSystemProvider vnext` can coexist with the existing one +the `FileSystemProviderV2` can coexist with the existing one and be exposed to the user as an experimental feature, so that PowerShell team can get early feedback and issue reports. At the same time, users can continue to use the existing `FileSystemProvider` -without being affected by the known or unknown issues introduced by the `FileSystemProvider vnext`. +without being affected by the known or unknown issues introduced by the `FileSystemProviderV2`. ## Specification -### Experimental Feateure Names +### Experimental Feature Names Experimental features for PowerShell components and built-in modules should be named as `PSXXX`, where `XXX` is the real feature name, which is a single word in camel case. @@ -99,7 +99,7 @@ while the automatic variable makes it easy to query from PowerShell scripts. #### Code Path Divided The first implementation scenario of an experimental feature is to divide the code path based on whether a feature is enabled. -This probably would be the most common pattern for implementing any experimental fatures. +This probably would be the most common pattern for implementing any experimental features. PowerShell exposes the enabled experimental feature names through both an API and an automatic variable, so it could be very easy for both C# code and PowerShell script to access this information. @@ -114,7 +114,7 @@ There are two cases in this scenario: - A new cmdlet is added by the experimental feature. The new cmdlet should be exposed when the experimental feature is enabled. -- A new cmdlet is added by the experimental feature to overide an existing cmdlet with the same name. +- A new cmdlet is added by the experimental feature to override an existing cmdlet with the same name. When the experimental feature is enabled, the existing cmdlet should be hidden and the new cmdlet should be exposed. @@ -161,7 +161,7 @@ The following code snippets are examples of using this attribute for cmdlet/func ```c# [Experimental("PSWebCmdletV2", FeatureAction.Show)] [Cmdlet(Verbs.Invoke, "WebRequest")] -public class InvokeWebRequsetCommandV2 : WebCmdletBaseV2 { ... } +public class InvokeWebRequestCommandV2 : WebCmdletBaseV2 { ... } [Experimental("PSWebCmdletV2", FeatureAction.Hide)] [Cmdlet(Verbs.Invoke, "WebRequest")] @@ -195,7 +195,7 @@ We want to expose this parameter only if the experimental feature is enabled. An example for the second case: > We want to remove a problematic parameter from `Add-Type`. -In order to evaluate the impact of the breaking chagne, +In order to evaluate the impact of the breaking change, we want to hide it only if the experimental feature is enabled. The same `[Experimental()]` attribute can also be used in this scenario. @@ -245,7 +245,7 @@ There are two cases in this scenario: An example for the first case: > We want to support a new transport layer for `Invoke-Command`, which will introduce a new parameter set. -Parameters `-ScirptBlock` and `-FilePath` need to be added to the new parameter set +Parameters `-ScriptBlock` and `-FilePath` need to be added to the new parameter set when the experimental feature is enabled. An example for the second case: @@ -339,6 +339,7 @@ internal enum EngineExperimentalFeatures PSFileSystemProviderV2 } ``` + ```xml // EngineExperimentalFeature.resx From 1a3d4df0f9f88090387e22ddbe1f4175750dda59 Mon Sep 17 00:00:00 2001 From: Dan Travison Date: Fri, 23 Feb 2018 10:58:35 -0800 Subject: [PATCH 4/9] Incorporate further discussions to the RFC (#1) We went through and discussed all comments in this PR. The discussion has been incorporated into the RFC. --- .../RFCNNNN-Support-Experimental-Features.md | 293 ++++++++++++++---- 1 file changed, 225 insertions(+), 68 deletions(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index a7a0d516..3baad8bf 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -63,7 +63,7 @@ The recommended naming conversion is to use the module name followed by a single For example: ```none -ModuleName.FeatureName +ModuleName.ExperimentName ``` ### Enable Experimental Features @@ -75,13 +75,19 @@ The `JSON` schema should look like this: ```json { "ExperimentalFeatures": [ - "A-Feature-name", - "Another-Feature-Name" + "A-Experimental-Feature-name", + "Another-Experimental-Feature-Name" ] } ``` -PowerShell reads the experimental feature list from the configuration file only once when starting up. +> NOTE: the current proposal is to only use the instance configuration file `$PSHOME\powershell.config.json` +(or the settings file passed in through `pwsh -settingsFile`, which overrides the instance configuration file) +to read the enabled experimental feature list. +The per-user configuration file will not be used in the initial implementation. + +PowerShell reads the experimental feature list from the configuration file only once before the engine starts. +So users cannot change experimental features at runtime. - For `pwsh`, the list will be read in `ConsoleHost` as soon as possible, so that experimental features targeting the `ConsoleHost` can be turned on early enough. @@ -90,17 +96,33 @@ PowerShell reads the experimental feature list from the configuration file only This is to make sure experimental features can be enabled when an application is hosting PowerShell. Feature names from the list will be populated to a read-only immutable HashSet that is static to the whole process. -The HashSet will be exposed to users via an API as well as an automatic variable. -The API makes it easy to query for enabled features in C# code, +The HashSet will be exposed to users via APIs as well as an automatic variable. +The APIs make it easy to query for enabled features in C# code, while the automatic variable makes it easy to query from PowerShell scripts. +The read-only immutable HashSet will be a property of the `ExecutionContext` type, +named `EnabledExperimentalFeatures` for example. +Similar to how we handle the `LanguageMode` property, +the `EnabledExperimentalFeatures` property can be exposed from `Runspace` and `SessionState`, +both actually referencing the `EnabledExperimentalFeatures` property in `ExecutionContext`. + +Then in C#, you can query for the enabled experimental features with `runspace.EnabledExperimentalFeatures` +when hosting PowerShell, +or `this.SessionState.EnabledExperimentalFeatures` when authoring a cmdlet. + +In PowerShell script, the same information can be accessed using +`$ExecutionContext.SessionState.EnabledExperimentalFeatures`, +`$Host.Runspace.EnabledExperimentalFeatures`, +or `$PSCmdlet.SessionState.EnabledExperimentalFeatures`. +We can also add an automatic variable `$PSEnabledExperimentalFeatures` to provide easy access. + ### Experimental Feature Implementation Scenarios #### Code Path Divided The first implementation scenario of an experimental feature is to divide the code path based on whether a feature is enabled. This probably would be the most common pattern for implementing any experimental features. -PowerShell exposes the enabled experimental feature names through both an API and an automatic variable, +PowerShell exposes the enabled experimental feature names through both APIs and an automatic variable, so it could be very easy for both C# code and PowerShell script to access this information. #### Cmdlet and Parameter @@ -124,19 +146,19 @@ We want to expose this cmdlet only if this experimental feature is enabled. An example for the second case: > We want to rewrite the `Invoke-WebRequest` cmdlet completely from scratch. -We don't want to make changes directly to the existing code due to the risk of regression to the existing cmdelt. +We don't want to make changes directly to the existing code due to the risk of regression to the existing cmdlet. Instead, we prefer to write new code from scratch in a new C# type and replace the existing `Invoke-WebRequest` when this experimental feature is enabled. The proposal to this scenario is to add a new attribute `[Experimental()]` that can apply to a cmdlet. -The attribute takes two arguments: `FeatureName` and `FeatureAction`. +The attribute takes two arguments: `ExperimentName` and `ExperimentAction`. -- `FeatureName` is a string, indicating the experimental feature the attribute is associated with. -- `FeatureAction` is an enum with two members (shown as follows), +- `ExperimentName` is a string, indicating the experimental feature the attribute is associated with. +- `ExperimentAction` is an enum with two members (shown as follows), and it indicates the action to take on the cmdlet that has the attribute declared. ```c# -public enum FeatureAction +public enum ExperimentAction { Hide, Show @@ -159,11 +181,11 @@ During the Cmdlet Registration, PowerShell can decide whether to process or igno The following code snippets are examples of using this attribute for cmdlet/function: ```c# -[Experimental("PSWebCmdletV2", FeatureAction.Show)] +[Experimental("PSWebCmdletV2", ExperimentAction.Show)] [Cmdlet(Verbs.Invoke, "WebRequest")] public class InvokeWebRequestCommandV2 : WebCmdletBaseV2 { ... } -[Experimental("PSWebCmdletV2", FeatureAction.Hide)] +[Experimental("PSWebCmdletV2", ExperimentAction.Hide)] [Cmdlet(Verbs.Invoke, "WebRequest")] public class InvokeWebRequestCommand : WebCmdletBase { ... } ``` @@ -213,11 +235,11 @@ PowerShell can decide whether to process or ignore a given parameter with the fo The following code snippets are examples of using this attribute for cmdlet/function: ```c# -[Experimental("PSNewAddTypeCompilation", FeatureAction.Show)] +[Experimental("PSNewAddTypeCompilation", ExperimentAction.Show)] [Parameter(ParameterSet = "NewCompilation")] public CompilationParameters CompileParameters { ... } -[Experimental("PSNewAddTypeCompilation", FeatureAction.Hide)] +[Experimental("PSNewAddTypeCompilation", ExperimentAction.Hide)] [Parameter()] public CodeDom CodeDom { ... } ``` @@ -253,35 +275,39 @@ An example for the second case: For the parameters that are in the parameter set, they should be removed from the parameter set when the experimental feature is enabled. -The proposal to this scenario is to add two properties `FeatureName` and `FeatureAction` to the `[Parameter()]` attribute. +The proposal to this scenario is to add two properties `ExperimentName` and `ExperimentAction` to the `[Parameter()]` attribute. So a `Parameter` attribute can be associated with an experimental feature. When the experimental feature is enabled, -PowerShell can choose to process or ignore the `Parameter` attribute depending on the `FeatureAction`. +PowerShell can choose to process or ignore the `Parameter` attribute depending on the `ExperimentAction`. The following code snippets are examples of using the new properties in a `Parameter` attribute: ```c# [Parameter(Position = 1, Mandatory = true, ParameterSetName = "SetAboutToAdd", - FeatureName = "PSAddTheSet", - FeatureAction = FeatureAction.Show)] + ExperimentName = "PSAddTheSet", + ExperimentAction = ExperimentAction.Show)] [Parameter(Position = 1, Mandatory = true, ParameterSetName = "SetAboutToRemove", - FeatureName = "PSRemoveTheSet", - FeatureAction = FeatureAction.Hide)] + ExperimentName = "PSRemoveTheSet", + ExperimentAction = ExperimentAction.Hide)] public ScriptBlock ScriptBlock { ... } ``` ```powershell param( [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToAdd", - FeatureName = "PSAddTheSet", FeatureAction = "Show")] + ExperimentName = "PSAddTheSet", ExperimentAction = "Show")] [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToRemove", - FeatureName = "SetAboutToRemove", FeatureAction = "Hide")] + ExperimentName = "SetAboutToRemove", ExperimentAction = "Hide")] [string] $Name, ) ``` +Note that, if a parameter is declared with multiple `ParameterAttribute` that are all associated with experimental features respectively +and all those experimental features are not enabled, +then the parameter is considered not enabled and thus will be ignored when processing parameters. + #### PowerShell Class and Members PowerShell Class is generated during the script compilation, @@ -293,7 +319,7 @@ even if the experimental feature is not enabled. The proposal to this scenario is to also apply the `[Experimental()]` attribute to PowerShell Class and its members. When processing `TypeDefinitionAst`, PowerShell can filter out a `TypeDefinitionAst` node -depending on whether the experimental feature is enabled and the `FeatureAction` value. +depending on whether the experimental feature is enabled and the `ExperimentAction` value. The filtering will need to be done in the following places (could be more): - [`SymbolTable.AddTypesInScope(Ast ast)`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/SymbolResolver.cs#L168) @@ -306,7 +332,7 @@ so that there is no duplication of the filtering logic for the `[Experimental()] As for the type members with the `[Experimental()]` attributes, [`TypeDefiner`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/PSType.cs#L17) needs to be updated to process or skip a type member when processing a `TypeDefinitionAst` node, -depending on whether the experimental feature is enabled and the `FeatureAction` value. +depending on whether the experimental feature is enabled and the `ExperimentAction` value. #### Exposed Public APIs @@ -324,29 +350,26 @@ including the ones in the PowerShell engine as well as the ones from modules. #### PowerShell Engine Experimental Feature For experimental features in PowerShell engine, -an internal enum `EngineExperimentalFeatures` will be used to track the names of all available engine experimental features. -When adding a new experimental feature, its name needs to be added to the enum for the feature to be discoverable to users. -The feature description needs to be added as a resource string, -and the id of the resource string needs to be constructible from the feature name, -such as `XXX-Description` where `XXX` is the feature name. -Here is an example of the enum and a description resource string. +an internal static read-only collection `EngineExperimentalFeatures` will be used to track all available engine experimental features. +The items in the collection are of the type `ExperimentalFeature`, whose definition looks as follows: ```c# -internal enum EngineExperimentalFeatures +public class ExperimentalFeature { - None, - PSDomainSpecificLanguage, - PSFileSystemProviderV2 + // Feature name + public string Name { get; } + // Feature description + public string Description { get; } + // Feature source (e.g. PSEngine, or module name) + public string Source { get; } } -``` -```xml -// EngineExperimentalFeature.resx - - Rewrite file system provider for better performance. - +internal static readonly ReadOnlyCollection EngineExperimentalFeatures; ``` +When adding a new experimental feature, an instance representing the feature needs to be added to the collection for it to be discoverable to users. +The `Source` for engine experimental features should be specified as `PSEngine`. + #### Module Experimental Feature For experimental features in modules, @@ -356,10 +379,10 @@ Here is an example: ```powershell PrivateData = @{ - ExperimentalFeature = @{ - Name = "PSWebCmdletV2" - Description = "Rewrite the web cmdlets for better performance" - } + ExperimentalFeatures = @( + @{Name = "PSWebCmdletV2", Description = "Rewrite the web cmdlets for better performance"} + @{Name = "PSRestCmdletV2", Description = "Rewrite the REST API cmdlets for better performance"} + ) } ``` @@ -369,23 +392,9 @@ will be updated to incorporate this metadata to the resulted `PSModuleInfo` obje #### Get-ExperimentalFeature A new cmdlet `Get-ExperimentalFeature` will be added to return all available experimental features of a PowerShell session. -The returned experimental features are represented by the type `ExperimentalFeature`, -whose definition looks as follows: +The returned experimental features are represented by the type [`ExperimentalFeature`](#powerShell-engine-experimental-feature). -```c# -public class ExperimentalFeature -{ - // Feature name - public string Name { get; } - // Feature description - public string Description { get; } - // Feature source (e.g. module name) - public string Source { get; } -} -``` - -The cmdlet first goes through the enum members of `EngineExperimentalFeatures` to find all engine experimental features. -For each of them, the description resource string will be retrieved and the `Source` will be set as `PSEngine`. +The cmdlet first goes through the items in `EngineExperimentalFeatures` to find all engine experimental features. Then the cmdlet goes through all loaded modules and get the available experimental features from each module. For each of them, the `Name` and `Description` are from the metadata. The `Source` will be the module name. @@ -396,8 +405,6 @@ with the help of the module analysis code. ### Experimental Feature Dependencies -> NOTE: This section may not be in scope for the first iteration of our implementation. - An experimental feature in a module can depend on another experimental feature. The latter could be a PowerShell engine feature, or a feature from the same module, @@ -407,7 +414,22 @@ it's easy to tell whether it's a PowerShell engine feature or a feature from a module, and in the latter case, the module name. -When loading a module, if an experimental feature of the module is enabled, +There are two kinds of dependencies on an experimental feature: + +- No new experimental feature is declared, + but new functionality is turned on when the dependent experimental feature is enabled. + For example, the `ScriptAnalyzer` automatically turns on the `DomainSpecificLanguage` related rules + when the experimental feature `PSDomainSpecificLanguage` is enabled. +- A new experimental feature is declared, + and the new functionality is turned on only if the new experimental feature is enabled. + For example, `Pester` has an experimental implementation using the `DomainSpecificLanguage`, + and users can try it out only if having the new experimental feature enabled. + +For the first scenario, the dependency is implicit and nothing needs to be done on the engine side. +The new functionality will be turned on when the dependent experimental feature is enabled. + +For the second scenario, the dependency is explicit and should be declared in the module manifest. +When loading such a module, if an experimental feature of the module is enabled, PowerShell needs to check whether the dependencies are satisfied, in a way that's similar to how PowerShell processes the nested modules. Here is the high-level description of the scenarios: @@ -452,10 +474,145 @@ public class ExperimentalFeature ### Test Experimental Features -How to test the combination of various experimental features would be a hard problem to solve, -as well as a problem that we have to solve before using the experimental feature support. +The regular daily test pass should run without any experimental feature enabled. +However, our CI system should be able to run tests with a specific experimental feature turned on, +so that CI builds can verify PRs for an experimental feature work. + +The proposal is to support the tag `[Experiment=]` in our CI system, +where `` is an experimental feature name. +When having this tag in the commit message of the last PR commit, +our CI system will run tests in a PowerShell instance with the experimental feature enabled. + +The tests for an experimental feature should be written in one of the following ways: + +```powershell +Describe "Tests for Experimental Feature X" { + BeforeAll { + $skip = -not $PSEnabledExperimentalFeatures.Contains("X") + ... + } + + It "..." -Skip:$skip { + ... + } +} + +try { + $skip = -not $PSEnabledExperimentalFeatures.Contains("X") + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $skip + + Describe "Tests for Experimental Feature X" { + ... + } +} +finally { + $global:PSDefaultParameterValues = $originalDefaultParameterValues +} +``` + +### Experimental Feature Incompatibility Mitigation + +There will be cases where experimental features are incompatible either intentionally or unintentionally. + +- An example of an intentional incompatibility occurs when two alternative, but incompatible solutions, are being tested. +- An example of an unintended incompatibility can occur when two experimental features prove to be incompatible through usage. + +A minimal implementation will support defining a list of experimental feature pairs in the `powershell.config.json` file. +At startup, attempts to use incompatible features would produce warnings and consider both as disabled. + +An opportunistic implementation will support an exclusion value in the experimental feature declaration +for cases where an author is intentionally providing alternative/incompatible implementations. + +### Static Code Analysis + +Static code analysis support should be provided to address experimental feature authoring. +Two areas are particularly important: + +1. Validating whether an experimental feature is marked correctly. + A use case for this occurs when an experimental feature is referenced in a module but not, or incorrectly defined in the manifest. + From a script perspective, a `PSScriptAnalyzer` rule would be a reasonable solution. + From a compiled code perspective, a reflection-based tool may be needed. + +2. Validating whether references to an non-existing experimental feature have been removed. + At some point, the experiment is completed and either removed from or incorporated into the code base. + At this point, a tool to scan script or compiled code would be needed to ensure any references to the experiment have been removed. + +## Open Issues + +### Whether experimental features can be included in PowerShell GA releases + +It's arguable whether or not experimental features should be allowed in PowerShell GA releases. +Take C# an an instance, it has experimental language features while in the development of a new version. +However, when reaching the final release, +there won't be any experimental features -- a feature is either officially kept or removed from the final release. + +The benefit of not allowing experimental features in a GA release is to improve stability. +But the drawback is that we cannot get more usage and feedback for an experimental feature +before deciding to keep or cut the feature. +This is because preview releases of PowerShell usually get much less usage compared with the GA release. + +### Whether experimental feature dependencies should be parsed when reading the configuration + +When reading the experimental feature list from configuration file, +shall we parse the dependencies at the same time or defer the dependency resolution until module loading? + +As described in the [`Experimental Feature Dependencies`](#experimental-feature-dependencies) section, +an experimental feature may have a chain dependency on other experimental features. +In that section, the proposal is to check for dependencies at module loading time. + +Another proposal is to resolve the dependencies when loading the configuration file, +so that PowerShell can fail early when dependencies are not satisfied. +The fail-early idea is desired, +however, PowerShell loads the enabled experimental feature list at very early stage, +where the engine is not ready for the operations required for resolving dependencies, +such as module discovery. +Also, there will be an impact to the startup time if we do this resolution when loading the configuration file. +Lastly, the module that exposes an experimental feature may not be in the module path yet +when PowerShell starts, +in which case it's impossible to resolve the experimental feature name. + +### How to override the instance configuration file -- rewrite command-line parsing vs. use environment variable + +A new command-line flag `-settingsFile` was added to `pwsh` to allow +a user to override the instance configuration file `$PSHOME\powershell.config.json` using another configuration file. +This flag is very useful in testing the experimental features. + +However, there are some amount of work that occurs before the command-line parameter parsing, +so if that amount of work has experimental features, +it would be hard to flag them in testing using `-settingsFile`. +This problem is seen on Linux with logging configuration due to +the logging happens at a very early stage when PowerShell starts. + +There are two proposals here for solving this problem: + +1. Move the command-line parameter parsing earlier in the startup call-path. + The main parsing logic depends on other components like the `InitialSessionState`, + so it cannot be moved to a very early stage. + A possible solution is to parse the command-line parameters in two passes. + The first pass is to convert the arguments into a more structured format, + and process flags like `-settingsFile` at a very early stage. + The second pass is the main parsing logic. +1. Use an environment variable `PSSettingsFile` to point to the configuration file to use. + By using the environment variable, + overriding the instance configuration file can happen as early as the singleton instance of `PowerShellConfig` gets created, + which would be the first time PowerShell attempts to read the configuration file. + +The first proposal will cost way more than the second proposal. +Since the configuration file overriding functionality is mainly for testing, +the second proposal may be more efficient cost-wise. + +### How to run tests for each experimental feature regularly + +We need to have test pass for every experimental feature introduced in PowerShell built-in components regularly. +Code changes that are not part of an experimental feature work may cause regression to the experimental feature. +If we don't have test pass for every built-in experimental feature, +the experimental features may be broken without us knowing it. + +### Whether to have telemetry for experimental feature usage -> Not sure how to handle it yet ... +It may be good to have telemetry for the usage of experimental features. +This work may fall into the more general telemetry support in PowerShell. ## Alternate Proposals and Considerations From 2a83a4bac25bfb43e30741135f007b4c9786a217 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 23 Feb 2018 11:13:49 -0800 Subject: [PATCH 5/9] Add the property 'IsEnabled' to ExperimentalFeature type (#2) 'Get-ExperimentalFeature' returns the available 'ExperimentalFeature' instances. For the experimental features that are enabled, the property 'IsEnabled' should be set to true accordingly. --- .../RFCNNNN-Support-Experimental-Features.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index 3baad8bf..34380073 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -362,6 +362,8 @@ public class ExperimentalFeature public string Description { get; } // Feature source (e.g. PSEngine, or module name) public string Source { get; } + // Feature is enabled or not + public bool IsEnabled { get; } } internal static readonly ReadOnlyCollection EngineExperimentalFeatures; @@ -403,6 +405,9 @@ Lastly, the cmdlet can go through not-yet-loaded modules that are in the module in a similar way as `Get-Command` does, with the help of the module analysis code. +For the experimental features that are enabled, +the property `IsEnabled` will be set to `true` accordingly. + ### Experimental Feature Dependencies An experimental feature in a module can depend on another experimental feature. @@ -467,6 +472,8 @@ public class ExperimentalFeature public string Description { get; } // Feature source (e.g. module name) public string Source { get; } + // Feature is enabled or not + public bool IsEnabled { get; } // Features it depends on public ExperimentalFeature[] Dependency { get; } } @@ -593,7 +600,7 @@ There are two proposals here for solving this problem: The first pass is to convert the arguments into a more structured format, and process flags like `-settingsFile` at a very early stage. The second pass is the main parsing logic. -1. Use an environment variable `PSSettingsFile` to point to the configuration file to use. +2. Use an environment variable `PSSettingsFile` to point to the configuration file to use. By using the environment variable, overriding the instance configuration file can happen as early as the singleton instance of `PowerShellConfig` gets created, which would be the first time PowerShell attempts to read the configuration file. From 56ba6a651b89beaf1f87e7aabd904dc68b4d4971 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 23 Feb 2018 11:19:36 -0800 Subject: [PATCH 6/9] Update the 'Comments Due' date --- 2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index 34380073..c534e6be 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -5,7 +5,7 @@ Status: Draft SupercededBy: N/A Version: 1.0 Area: Engine and Configuration -Comments Due: March 2nd, 2018 +Comments Due: March 22nd, 2018 Plan to implement: Yes --- From e7fe22fd63ad1c4e46a76f367aa185e8807d64b5 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 8 Mar 2018 17:13:18 -0800 Subject: [PATCH 7/9] Address some comments --- .../RFCNNNN-Support-Experimental-Features.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index c534e6be..bfd1dbbe 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -393,8 +393,19 @@ will be updated to incorporate this metadata to the resulted `PSModuleInfo` obje #### Get-ExperimentalFeature -A new cmdlet `Get-ExperimentalFeature` will be added to return all available experimental features of a PowerShell session. +A new cmdlet `Get-ExperimentalFeature` will be added to return all experimental features of a PowerShell session. The returned experimental features are represented by the type [`ExperimentalFeature`](#powerShell-engine-experimental-feature). +The syntax signature of the cmdlet will look as follows: + +```powershell +Get-ExperimentalFeature [-Enabled] [-Disabled] [] + +Get-ExperimentalFeature [-Name] [] +``` + +When `-Enabled` is specified, only enabled experimental features will be returned. +When `-Disabled` is specified, only disabled experimental features will be returned. +When `-Name` is specified, only those specified experimental features will be returned. The cmdlet first goes through the items in `EngineExperimentalFeatures` to find all engine experimental features. From 6cf31045255776fd8f7de0e89db76a259e3d4d2b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 6 Aug 2018 18:20:25 -0700 Subject: [PATCH 8/9] Update the RFC --- .../RFCNNNN-Support-Experimental-Features.md | 190 ++++++++---------- 1 file changed, 87 insertions(+), 103 deletions(-) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md index bfd1dbbe..560a3ae8 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md @@ -86,35 +86,18 @@ The `JSON` schema should look like this: to read the enabled experimental feature list. The per-user configuration file will not be used in the initial implementation. -PowerShell reads the experimental feature list from the configuration file only once before the engine starts. -So users cannot change experimental features at runtime. - -- For `pwsh`, the list will be read in `ConsoleHost` as soon as possible, - so that experimental features targeting the `ConsoleHost` can be turned on early enough. -- When creating a Runspace, PowerShell will check if the list has been read, - and read the list if not yet. - This is to make sure experimental features can be enabled when an application is hosting PowerShell. +PowerShell reads the experimental feature list from the configuration file only once. +It's done in the static constructor of the type `ExperimentalFeature`, +so the initialization is guaranteed to take place before any experimental feature code path is hit. Feature names from the list will be populated to a read-only immutable HashSet that is static to the whole process. -The HashSet will be exposed to users via APIs as well as an automatic variable. -The APIs make it easy to query for enabled features in C# code, +The HashSet will be exposed to users via a public method `ExperimentalFeature.IsEnabled(string featureName)`, +as well as an automatic variable `$EnabledExperimentalFeatures`. +The method makes it easy to query for enabled features in C# code, while the automatic variable makes it easy to query from PowerShell scripts. -The read-only immutable HashSet will be a property of the `ExecutionContext` type, -named `EnabledExperimentalFeatures` for example. -Similar to how we handle the `LanguageMode` property, -the `EnabledExperimentalFeatures` property can be exposed from `Runspace` and `SessionState`, -both actually referencing the `EnabledExperimentalFeatures` property in `ExecutionContext`. - -Then in C#, you can query for the enabled experimental features with `runspace.EnabledExperimentalFeatures` -when hosting PowerShell, -or `this.SessionState.EnabledExperimentalFeatures` when authoring a cmdlet. - -In PowerShell script, the same information can be accessed using -`$ExecutionContext.SessionState.EnabledExperimentalFeatures`, -`$Host.Runspace.EnabledExperimentalFeatures`, -or `$PSCmdlet.SessionState.EnabledExperimentalFeatures`. -We can also add an automatic variable `$PSEnabledExperimentalFeatures` to provide easy access. +The read-only immutable HashSet is a static field in the type `ExperimentalFeature`, +named `EnabledExperimentalFeatureNames`. ### Experimental Feature Implementation Scenarios @@ -127,7 +110,7 @@ so it could be very easy for both C# code and PowerShell script to access this i #### Cmdlet and Parameter -The second implementation scenario is manipulate cmdlets and parameters. +The second implementation scenario is to manipulate cmdlets and parameters. There are three categories that we will discuss as follows. ##### Add New Cmdlets @@ -150,17 +133,23 @@ We don't want to make changes directly to the existing code due to the risk of r Instead, we prefer to write new code from scratch in a new C# type and replace the existing `Invoke-WebRequest` when this experimental feature is enabled. -The proposal to this scenario is to add a new attribute `[Experimental()]` that can apply to a cmdlet. -The attribute takes two arguments: `ExperimentName` and `ExperimentAction`. +The proposal to this scenario is to add a new attribute `ExperimentalAttribute` that can be applied to a cmdlet. +The attribute takes two arguments: `experimentName` and `experimentAction`. -- `ExperimentName` is a string, indicating the experimental feature the attribute is associated with. -- `ExperimentAction` is an enum with two members (shown as follows), +- `experimentName` is a string, indicating the experimental feature the attribute is associated with. +- `experimentAction` is an enum with three members, and it indicates the action to take on the cmdlet that has the attribute declared. ```c# public enum ExperimentAction { + // Represent an undefined action, used as the default value only. + None, + + // Hide the cmdlet/parameter when the corresponding experimental feature is enabled. Hide, + + // Show the cmdlet/parameter when the corresponding experimental feature is enabled. Show } ``` @@ -170,13 +159,13 @@ and create cmdlets for the cmdlet/function implementations defined in those sour During the Cmdlet Registration, PowerShell can decide whether to process or ignore a cmdlet with the following workflow: -- Check if the `[Experimental()]` attribute is declared +- Check if `ExperimentalAttribute` is declared - if so, check if the associated experimental feature is enabled - if so, ignore the cmdlet if the action to take is `Hide`; process the cmdlet if the action to take is `Show`. - if not, ignore the cmdlet if the action to take is `Show`; process the cmdlet if the action to take is `Hide` - - if not, process the cmdlet as is today + - if not, process the cmdlet as if no `ExperimentalAttribute` is declared. The following code snippets are examples of using this attribute for cmdlet/function: @@ -199,7 +188,7 @@ function Enable-SSHRemoting { } ``` -> Implementation Note: the attribute `[Experimental()]` could be applied to a script block directly. +> Implementation Note: the attribute `ExperimentalAttribute` could be applied to a script block directly. Execution of such a script block should error out when the associated experimental feature is not enabled. ##### Add New Parameters @@ -220,17 +209,17 @@ An example for the second case: In order to evaluate the impact of the breaking change, we want to hide it only if the experimental feature is enabled. -The same `[Experimental()]` attribute can also be used in this scenario. +The same `ExperimentalAttribute` can also be used in this scenario. During the Cmdlet Registration, when processing parameters for a cmdlet/function, PowerShell can decide whether to process or ignore a given parameter with the following workflow: -- Check if the `[Experimental()]` attribute is declared +- Check if `ExperimentalAttribute` is declared - if so, check if the associated experimental feature is enabled - if so, ignore the parameter if the action to take is `Hide`; process the parameter if the action to take is `Show`. - if not, ignore the parameter if the action to take is `Show`; process the parameter if the action to take is `Hide` - - if not, process the parameter as is today + - if not, process the parameter as if no `ExperimentalAttribute` is declared. The following code snippets are examples of using this attribute for cmdlet/function: @@ -275,31 +264,23 @@ An example for the second case: For the parameters that are in the parameter set, they should be removed from the parameter set when the experimental feature is enabled. -The proposal to this scenario is to add two properties `ExperimentName` and `ExperimentAction` to the `[Parameter()]` attribute. -So a `Parameter` attribute can be associated with an experimental feature. +The proposal to this scenario is to add two properties `ExperimentName` and `ExperimentAction` to `ParameterAttribute`. +So a `ParameterAttribute` can be associated with an experimental feature. When the experimental feature is enabled, -PowerShell can choose to process or ignore the `Parameter` attribute depending on the `ExperimentAction`. +PowerShell can choose to process or ignore the `ParameterAttribute` depending on the `ExperimentAction`. The following code snippets are examples of using the new properties in a `Parameter` attribute: ```c# -[Parameter(Position = 1, Mandatory = true, - ParameterSetName = "SetAboutToAdd", - ExperimentName = "PSAddTheSet", - ExperimentAction = ExperimentAction.Show)] -[Parameter(Position = 1, Mandatory = true, - ParameterSetName = "SetAboutToRemove", - ExperimentName = "PSRemoveTheSet", - ExperimentAction = ExperimentAction.Hide)] +[Parameter("PSAddTheSet", ExperimentAction.Show, Position = 1, Mandatory = true, ParameterSetName = "SetAboutToAdd")] +[Parameter("PSRemoveTheSet", ExperimentAction.Hide, Position = 1, Mandatory = true, ParameterSetName = "SetAboutToRemove")] public ScriptBlock ScriptBlock { ... } ``` ```powershell param( - [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToAdd", - ExperimentName = "PSAddTheSet", ExperimentAction = "Show")] - [Parameter(Mandatory = $true, ParameterSetName = "SetAboutToRemove", - ExperimentName = "SetAboutToRemove", ExperimentAction = "Hide")] + [Parameter("PSAddTheSet", "Show", Mandatory = $true, ParameterSetName = "SetAboutToAdd")] + [Parameter("SetAboutToRemove", "Hide", Mandatory = $true, ParameterSetName = "SetAboutToRemove")] [string] $Name, ) ``` @@ -310,14 +291,15 @@ then the parameter is considered not enabled and thus will be ignored when proce #### PowerShell Class and Members +> NOTE: Not implemented in the first iteration of the feature work. + PowerShell Class is generated during the script compilation, so runtime checks have no control over it. If an experimental feature of a module involves new declaration of PowerShell Class, the PowerShell Class will be exposed through `using Module ` even if the experimental feature is not enabled. -The proposal to this scenario is to also apply the `[Experimental()]` attribute -to PowerShell Class and its members. +The proposal to this scenario is to also apply the `ExperimentalAttribute` to PowerShell Class and its members. When processing `TypeDefinitionAst`, PowerShell can filter out a `TypeDefinitionAst` node depending on whether the experimental feature is enabled and the `ExperimentAction` value. The filtering will need to be done in the following places (could be more): @@ -328,9 +310,9 @@ The filtering will need to be done in the following places (could be more): - [`CompletionCompleters.CompleteType(CompletionContext context, string prefix = "", string suffix = "")`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs#L5844) The searching for `TypeDefinitionAst` nodes should be done in one method, -so that there is no duplication of the filtering logic for the `[Experimental()]` attribute. +so that there is no duplication of the filtering logic for `ExperimentalAttribute`. -As for the type members with the `[Experimental()]` attributes, +As for the type members with the `ExperimentalAttribute` attributes, [`TypeDefiner`](https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/PSType.cs#L17) needs to be updated to process or skip a type member when processing a `TypeDefinitionAst` node, depending on whether the experimental feature is enabled and the `ExperimentAction` value. @@ -363,7 +345,7 @@ public class ExperimentalFeature // Feature source (e.g. PSEngine, or module name) public string Source { get; } // Feature is enabled or not - public bool IsEnabled { get; } + public bool Enabled { get; } } internal static readonly ReadOnlyCollection EngineExperimentalFeatures; @@ -376,21 +358,29 @@ The `Source` for engine experimental features should be specified as `PSEngine`. For experimental features in modules, metadata about an experimental feature should be kept in the module manifest. -We use the `PrivateData` section of a module manifest to expose the experimental features from the module. +We use the `PrivateData.PSData` section of a module manifest to expose the experimental features from the module. Here is an example: ```powershell PrivateData = @{ - ExperimentalFeatures = @( - @{Name = "PSWebCmdletV2", Description = "Rewrite the web cmdlets for better performance"} - @{Name = "PSRestCmdletV2", Description = "Rewrite the REST API cmdlets for better performance"} - ) + PSData = @{ + ExperimentalFeatures = @( + @{Name = "PSWebCmdletV2", Description = "Rewrite the web cmdlets for better performance"} + @{Name = "PSRestCmdletV2", Description = "Rewrite the REST API cmdlets for better performance"} + ) + } } ``` PowerShell module components, such as `Import-Module` and module analysis, will be updated to incorporate this metadata to the resulted `PSModuleInfo` object. +The property `ExperimentalFeatures` of the type `ReadOnlyCollection` is added to `PSModuleInfo`, +to hold the exposed features from the module. + +When any experimental feature are enabled, a unique file name will be used for caching the module analysis results, +so that the experimental commands won't affect a PowerShell session that has no experimental feature enabled. + #### Get-ExperimentalFeature A new cmdlet `Get-ExperimentalFeature` will be added to return all experimental features of a PowerShell session. @@ -398,29 +388,19 @@ The returned experimental features are represented by the type [`ExperimentalFea The syntax signature of the cmdlet will look as follows: ```powershell -Get-ExperimentalFeature [-Enabled] [-Disabled] [] - -Get-ExperimentalFeature [-Name] [] +Get-ExperimentalFeature [[-Name] ] [-ListAvailable] [] ``` -When `-Enabled` is specified, only enabled experimental features will be returned. -When `-Disabled` is specified, only disabled experimental features will be returned. -When `-Name` is specified, only those specified experimental features will be returned. - -The cmdlet first goes through the items in `EngineExperimentalFeatures` to find all engine experimental features. - -Then the cmdlet goes through all loaded modules and get the available experimental features from each module. -For each of them, the `Name` and `Description` are from the metadata. The `Source` will be the module name. - -Lastly, the cmdlet can go through not-yet-loaded modules that are in the module path, -in a similar way as `Get-Command` does, -with the help of the module analysis code. +By default, it returns enabled experimental features only. +When `-ListAvailable` is specified, it returns all available experimental features. For the experimental features that are enabled, -the property `IsEnabled` will be set to `true` accordingly. +the property `Enabled` will be set to `true` accordingly. ### Experimental Feature Dependencies +> NOTE: Not implemented in the first iteration of the feature work. + An experimental feature in a module can depend on another experimental feature. The latter could be a PowerShell engine feature, or a feature from the same module, @@ -464,10 +444,12 @@ the metadata of an experimental feature in `.psd1` needs to add a `Dependency` f ```powershell PrivateData = @{ - ExperimentalFeature = @{ - Name = "PSWebCmdletV2" - Description = "Rewrite the web cmdlets for better performance" - Dependency = "", "..." + PSData = @{ + ExperimentalFeature = @{ + Name = "PSWebCmdletV2" + Description = "Rewrite the web cmdlets for better performance" + Dependency = "", "..." + } } } ``` @@ -484,7 +466,7 @@ public class ExperimentalFeature // Feature source (e.g. module name) public string Source { get; } // Feature is enabled or not - public bool IsEnabled { get; } + public bool Enabled { get; } // Features it depends on public ExperimentalFeature[] Dependency { get; } } @@ -496,10 +478,14 @@ The regular daily test pass should run without any experimental feature enabled. However, our CI system should be able to run tests with a specific experimental feature turned on, so that CI builds can verify PRs for an experimental feature work. -The proposal is to support the tag `[Experiment=]` in our CI system, -where `` is an experimental feature name. -When having this tag in the commit message of the last PR commit, -our CI system will run tests in a PowerShell instance with the experimental feature enabled. +The proposal is as follows: + +- Add the file `test/tools/TestMetadata.json` for declaring tests for experimental features. + - Key is the experimental feature name; + - Value is an array of paths for the folder or files that Pester should run when the feature is turned on. + If the value is an empty array, then all tests will be executed for the feature. +- Add `-ExperimentalFeatureName` to `Start-PSPester` to support running tests with the specified feature turned on. +- Update `AppVeyor.psm1` and `travis.ps1` to read `TestMetadata.json` and run tests for each declared features. The tests for an experimental feature should be written in one of the following ways: @@ -531,6 +517,8 @@ finally { ### Experimental Feature Incompatibility Mitigation +> NOTE: Not implemented in the first iteration of the feature work. + There will be cases where experimental features are incompatible either intentionally or unintentionally. - An example of an intentional incompatibility occurs when two alternative, but incompatible solutions, are being tested. @@ -544,6 +532,8 @@ for cases where an author is intentionally providing alternative/incompatible im ### Static Code Analysis +> NOTE: Not implemented in the first iteration of the feature work. + Static code analysis support should be provided to address experimental feature authoring. Two areas are particularly important: @@ -558,17 +548,12 @@ Two areas are particularly important: ## Open Issues -### Whether experimental features can be included in PowerShell GA releases +### Whether experimental features can be included in PowerShell GA releases (Closed) -It's arguable whether or not experimental features should be allowed in PowerShell GA releases. -Take C# an an instance, it has experimental language features while in the development of a new version. -However, when reaching the final release, -there won't be any experimental features -- a feature is either officially kept or removed from the final release. +The conclusion is: it's OK to have experimental features included in PowerShell GA releases. -The benefit of not allowing experimental features in a GA release is to improve stability. -But the drawback is that we cannot get more usage and feedback for an experimental feature -before deciding to keep or cut the feature. -This is because preview releases of PowerShell usually get much less usage compared with the GA release. +This allows the development of an experimental feature to span across GA releases, +and also allows an experimental feature to get more feedback. ### Whether experimental feature dependencies should be parsed when reading the configuration @@ -620,17 +605,16 @@ The first proposal will cost way more than the second proposal. Since the configuration file overriding functionality is mainly for testing, the second proposal may be more efficient cost-wise. -### How to run tests for each experimental feature regularly +### How to run tests for each experimental feature regularly (Closed) + +The proposed [CI updates](#test-experimental-features) will enable running tests for each experimental feature in daily builds. -We need to have test pass for every experimental feature introduced in PowerShell built-in components regularly. -Code changes that are not part of an experimental feature work may cause regression to the experimental feature. -If we don't have test pass for every built-in experimental feature, -the experimental features may be broken without us knowing it. +### Whether to have telemetry for experimental feature usage (Closed) -### Whether to have telemetry for experimental feature usage +The conclusion is: yes, we need telemetry for experimental feature usage. -It may be good to have telemetry for the usage of experimental features. -This work may fall into the more general telemetry support in PowerShell. +However, more work (compliance and etc.) needs to be done to enable telemetry in PowerShell Core. +So for now, we don't have telemetry for experimental feature usage. ## Alternate Proposals and Considerations From 950be17cb91a85f1279b662b51b156a3a5903554 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 8 Aug 2018 15:36:34 -0700 Subject: [PATCH 9/9] Update and rename 2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md to 4-Experimental-Accepted/RFC0029-Support-Experimental-Features.md --- .../RFC0029-Support-Experimental-Features.md | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) rename 2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md => 4-Experimental-Accepted/RFC0029-Support-Experimental-Features.md (98%) diff --git a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md b/4-Experimental-Accepted/RFC0029-Support-Experimental-Features.md similarity index 98% rename from 2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md rename to 4-Experimental-Accepted/RFC0029-Support-Experimental-Features.md index 560a3ae8..e53cb677 100644 --- a/2-Draft-Accepted/RFCNNNN-Support-Experimental-Features.md +++ b/4-Experimental-Accepted/RFC0029-Support-Experimental-Features.md @@ -1,5 +1,5 @@ --- -RFC: RFC +RFC: RFC0029 Author: Dongbo Wang, Dan Travison Status: Draft SupercededBy: N/A @@ -619,3 +619,28 @@ So for now, we don't have telemetry for experimental feature usage. ## Alternate Proposals and Considerations No alternate proposals. + +--------------- +## PowerShell Committee Decision + +### Voting Results + +Joey Aiello: Accept + +Bruce Payette: Accept + +Steve Lee: Accept + +Hemant Mahawar: Accept + +Dongbo Wang: Accept + +Kenneth Hansen: Accept + +### Majority Decision + +Commmittee agrees that this RFC satisfies the experimental feature flag feature and recognizes that not all aspects of this RFC are currently implemented. + +### Minority Decision + +N/A