Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App-configurable compatibility options spec: CompatibilityOptions #4966

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions specs/Compatibility/CompatibilityOptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
App-configurable compatibility options
===

# Background

_This spec adds a CompatibilityOptions class to control behavior of servicing changes._
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RuntimeCompatibilityOptions?

AppConfigurableCompatibility?

(Naming, hard :-()


Apps using Windows App SDK have two choices today: use Windows App SDK as an
automatically-updating framework package or use Windows App SDK in self-contained mode with no
automatic updates. Apps which use the framework package automatically get fixes, which is great,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not 'automatic updates' but WHO does the servicing. MSIX packages routinely get serviced by Microsoft and others (whoever gets an update to the PC first) vs self-contained copies are serviced by the app/developer. Apps using non-MSIX technologies may perform automatic updates of their dependencies, but it's entirely dependent on the app developer on when (or even if) to do so.

SUGGESTION: ...in self-contained mode updated only if/when the containing application/publisher decides to update its copy of Windows App SDK.

(or words to that effect)

but they have a chance of being broken by a bug or subtle behavior change in a servicing update.
Some apps choose self-contained mode to avoid that risk of an automatic servicing update breaking
them. But even when apps choose self-contained mode, they may find that they can't update to a
newer servicing release due to a bug or behavior change. Also, self-contained apps can't use
features which require shared services, such as some Notification features delivered via the
Singleton package.

App-configurable compatibility is intended to prevent these problems, enabling apps to choose to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'App-configurable compatibility' - seems to be the catchy term for this feature. API naming should match?

use the framework package with confidence they won't be broken. Also, apps which find a
compatibility problem in a servicing release will still be able to move forward by temporarily
disabling problematic changes. Even apps using self-contained can update to a new package with
confidence that app-configurable compatibility will ensure a successful update.

A new `CompatibilityOptions` class has APIs to control the behavior of servicing changes.
There are also properties which can be set in the app's project file to automatically use the new
APIs with the specified values.

# API Pages

## CompatibilityOptions class

Configures which changes in Windows App SDK servicing releases are enabled. By default, all
changes are enabled, but `CompatibilityOptions` can be used to lock the runtime behavior to a
specified patch version or to disable specific changes:

1. **Choose the patch version:** You can specify which servicing patch version's behavior you
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patch version

API and XML refer to "patch mode". Different phrasing for same thing or there's a meaningful difference in the lingo?

If same then please use consistent terminology. If not then please define "patch version" and "patch mode" so it's clear how they differ

want to use. For example, your app can specify that it wants the 1.7.2 version behavior, which
will have Windows App SDK run in that mode even if 1.7.3 or later is installed. This capability
allows you to control when your app gets new fixes or behavior changes, even when not using
self-contained mode.
2. **Temporarily disable specific changes:** If your app encounters a problem with a specific
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporarily disable specific changes

These changes are the developer's request or hint that, as you note, may not be disableable in time. But isn't that the same for patch version/mode? If I specify patch version = 1.7.2 and eventually update to use 1.8 isn't my previous 1.7 request irrelevant?

The intent is "disable" means "disable...the things allowed to be disabled", which could change on each build (formerly disableable now aren't)? Hence the 'temporary'? Or no, once a 'disable' knob is present in a major.minor it will be supported disable-able forever in that major.minor?

SUGGESTION: Remove 'Temporarily'

change in a servicing update, you can disable just that change while still benefiting from the
other changes or features in that update. (All changes are enabled by default for the patch
version in use.) Disabling a change is a temporary measure which gives time for a fix to be
released in a future Windows App SDK update or for you to implement an update in your app.

Here's an example of using `CompatibilityOptions` to specify a patch version and disable a
specific change:

```c#
void ApplyCompatibilityOptions()
{
var compatibilityOptions = new CompatibilityOptions();
compatibilityOptions.PatchMode1 = new WindowsAppRuntimeVersion(1,7,3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I specify a future version e.g. I specified WindowsAppRuntimeVersion(1,7,3) but I'm running with 1.7.2? Is that an error or is any future patch value considered equivalent e.g. running with 1.7.3 I can specify 1.7.3, 1.7.4, 1.7.9999, 1.7.65535, etc all equivalent?

Where are the rules for how specified version vs runtime/actual version spelled out?

SUGGESTION: Expand this section with more details about versioning rules

compatibilityOptions.PatchMode2 = new WindowsAppRuntimeVersion(1,8,2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I'm running with 1.7.3 and only specified 1.8.anything? That's equivalent to specifying nothing?

compatibilityOptions.DisabledChanges.Add(CompatibilityChange.HypotheticalChange);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is HypotheticalChange an actual thing or it's representative of an actual value? If the latter please use a more real example

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if I specify an unknown change? Silently ignored? Error? Debug message? Other?

CONSIDER: Option(s) for developers to be aware of unknown inputs e.g.

enum UnknownDisabledChangeSeverity { Verbose, Information, Error }
...
compatibilityOptions.UnknownDisabledChangeSeverity(UnknownDisabledChangeSeverity.Error);

or

compatibilityOptions.OnUnknownDisabledChangeOutputIfDebuggerAttached(true);, compatibilityOptions.OnUnknownDisabledChangeFailFast(true); etc (similar to Bootstrapper options like enum MddBootstrapInitializeOptions)

compatibilityOptions.Apply();
}
```

Note that CompatibilityOptions must be applied early in the process before any other Windows App
SDK APIs are called, or right after initializing the Windows App Runtime.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initializing the Windows App Runtime

What does this mean? When does this occur? There is no WindowsAppRuntimeInitialize() function (intentionally)

Do you mean the bootstrapper APIs? Other APIs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before any other Windows App SDK APIs are called

Does that include before any WinAppSDK 'auto-initializer' code is executed?


### Specifying CompatibilityOptions in the app's project file

You can also specify the patch version and disabled changes in the app's project file instead of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"patch version" but XML and API (above) say "patch mode"

Please use consistent terminology

directly calling the CompatibilityOptions APIs. This approach has the advantage of ensuring the
options are applied early at the proper timing. Here's an example of how to specify the patch
version and disabled changes in the project file (such as `.csproj` or `.vcxproj`):

```xml
<PropertyGroup>
<WindowsAppSDKRuntimePatchMode>1.7.3</WindowsAppSDKRuntimePatchMode>
<WindowsAppSDKRuntimePatchMode2>1.8.2</WindowsAppSDKRuntimePatchMode2>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is WindowsAppSDKRuntimePatchMode specific to 1.7.* and WindowsAppSDKRuntimePatchMode2 specific to 1.8.*? Will 1.9 add a WindowsAppSDKRuntimePatchMode3?, 1.10 add ...PatchMode4, etc?

<WindowsAppSDKDisabledChanges>HypotheticalChange, OtherHypotheticalChange</WindowsAppSDKDisabledChanges>
</PropertyGroup>
```

The `WindowsAppSDKDisabledChanges` property is a comma-separated list of `CompatibilityChange`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comma-separated list

Why not multiple elements? e.g.

<WindowsAppSDKDisabledChanges>
    <Change>HypotheticalChange</>
    <Change>OtherHypotheticalChange</>
</>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comma-separated? So semi-colons or other characters are errors?

<WindowsAppSDKDisabledChanges>HypotheticalChange; OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges>HypotheticalChange$ OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges>HypotheticalChange. OtherHypotheticalChange</>

Whitespace is signficant? Are any of these errors or all equivalent?

<WindowsAppSDKDisabledChanges>HypotheticalChange,OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges>HypotheticalChange, OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges>HypotheticalChange ,OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges>HypotheticalChange , OtherHypotheticalChange</>
<WindowsAppSDKDisabledChanges> HypotheticalChange , OtherHypotheticalChange </>

values to disable.

### Behavior with no PatchMode specified

If no `PatchMode1` or `PatchMode2` is specified, or if neither value matches the major.minor
version of the runtime being used, then the runtime will use the latest patch version. In other
words, the runtime will run with all servicing changes enabled, which is the same behavior as
Windows App SDK 1.6 and earlier.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phrasing of 1.6 mention is confusing. The intent is the latest behavior is enabled, just like before this API existed?

SUGGESTION: "In other words, the runtime will run with all servicing changes enabled (just like how Windows App SDK worked before this API existed)."


## CompatibilityOptions.PatchMode1

Optional patch mode to use if the runtime version matches the major.minor version. If the runtime
version does not match the specified major.minor version, this value is ignored.

Instead of directly using this API, this patch mode could be specified in the app's project file,
like this:

```xml
<PropertyGroup>
<WindowsAppSDKRuntimePatchMode>1.7.3</WindowsAppSDKRuntimePatchMode>
</PropertyGroup>
```

If your app is not in the process of transitioning to a new version of the Windows App SDK, you
should only set this one patch mode.

## CompatibilityOptions.PatchMode2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PatchMode2 name is misleading. It's a second meant for transition period. But couldn't I accomplish same with build logic and even more flexibly e.g.

<WindowsAppSDKRuntimePatchMode Condition="$([System.Text.RegularExpressions.Regex]::Match($(WindowsAppSDKVersion), '^\d+\.\d+')==1.7">1.7.3</WindowsAppSDKRuntimePatchMode>
<WindowsAppSDKRuntimePatchMode Condition="$([System.Text.RegularExpressions.Regex]::Match($(WindowsAppSDKVersion), '^\d+\.\d+')==1.8">1.8.2</WindowsAppSDKRuntimePatchMode>
<WindowsAppSDKRuntimePatchMode Condition="$([System.Text.RegularExpressions.Regex]::Match($(WindowsAppSDKVersion), '^\d+\.\d+')==1.9">1.9.5</WindowsAppSDKRuntimePatchMode>

or in code via similar e.g. in *.vcxproj

<PreprocessorDefinitions Condition="$([System.Text.RegularExpressions.Regex]::Match($(WindowsAppSDKVersion), '^\d+\.\d+')==1.7">MY_WINAPPSDK_PATCHMODE_MAJOR=1;MY_WINAPPSDK_PATCHMODE_MINOR=7;MY_WINAPPSDK_PATCHMODE_PATCH=3;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$([System.Text.RegularExpressions.Regex]::Match($(WindowsAppSDKVersion), '^\d+\.\d+')==1.8">MY_WINAPPSDK_PATCHMODE_MAJOR=1;MY_WINAPPSDK_PATCHMODE_MINOR=8;MY_WINAPPSDK_PATCHMODE_PATCH=2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
...

and in code

compatibilityOptions.PatchMode = new WindowsAppRuntimeVersion(MY_WINAPPSDK_PATCHMODE_MAJOR, MY_WINAPPSDK_PATCHMODE_MINOR, MY_WINAPPSDK_PATCHMODE_PATCH);


Optional patch mode to use if the runtime version matches the major.minor version. If the runtime
version does not match the specified major.minor version, this value is ignored.

This property enables setting a second patch mode to help apps transitioning to a new version of
the Windows App SDK. This is a convenience to allow the patch modes for both the old and new
version to be specified during the transition. Apps not in the process of transitioning should
only set the one patch mode they want to use.

Setting both patch modes for the same major.minor version, such as 1.7.3 and 1.7.4, is not allowed
and will generate an error during `Apply()`.

Instead of directly using this API, this patch mode could be specified in the app's project file,
like this:

```xml
<PropertyGroup>
<WindowsAppSDKRuntimePatchMode2>1.8.2</WindowsAppSDKRuntimePatchMode2>
</PropertyGroup>
```

## CompatibilityOptions.DisabledChanges

Optional list of specific servicing changes to disable.

If you encounter a problem with a specific change in a servicing update, you can disable just that
change by adding it to the `DisabledChanges` list before calling `Apply`, or by specifying it in
your app's project file:
```xml
<PropertyGroup>
<WindowsAppSDKDisabledChanges>HypotheticalChange, OtherHypotheticalChange</WindowsAppSDKDisabledChanges>
</PropertyGroup>
```

The [release notes](https://learn.microsoft.com/windows/apps/windows-app-sdk/stable-channel) for
the Windows App SDK will list the name of each change that can be disabled.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a list be provided in the SDK for build-time reference? e.g.

Add AppConfigurableCompatibility.json into the WinAppSDK nuget containing

{
  "AppConfigurableCompatibility": {
    "DisabledChanges": [
      {
        "DisabledChange": "HypotheticalChange",
        "Description": "Blah blah blah",
        "Version(s)": [
          "1.7.2+"
        ],
        "URL": "https://github.com/microsoft/WindowsAppSDK/docs/appconfigurablecompatibility/HypotheticalChange"
      },
      {
        "DisabledChange": "OtherHypotheticalChange",
        "Description": "Yadda yadda",
        "Version(s)": [
          "1.7.3 - 1.7.6"
        ],
        "URL": "https://github.com/microsoft/WindowsAppSDK/docs/appconfigurablecompatibility/OtherHypotheticalChange"
      },
      {
        "DisabledChange": "YetAnotherHypotheticalChange",
        "Description": "Booyah...",
        "Version(s)": [
          "1.7.3",
          "1.7.5+"
        ],
        "URL": "https://github.com/microsoft/WindowsAppSDK/docs/appconfigurablecompatibility/YetAnotherHypotheticalChange"
      }
    ]
  }
}


Note that disabling a change is a temporary measure which gives time for a fix to be released in a
future Windows App SDK update or for you to implement an update in your app. You should report
changes you disable if you believe it to be a bug in the Windows App SDK. This will help the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are disabled changes reported via insights?

Windows App SDK team to understand the impact of the change and prioritize a fix. You can report
issues on the [WindowsAppSDK GitHub repo](https://github.com/microsoft/WindowsAppSDK/issues/new?template=bug-report.yaml).

The capability to disable a change is **not** available across new Windows App SDK stable
releases (such as 1.7.x to 1.8.x), so this does not allow you to permanently disable an
intentional behavior change. Also, new stable releases, such as 1.8.0, start with no changes
available to disable -- this capability only applies to disabling targeted servicing changes
added in servicing releases.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies DisabledChanges are version-specific?

Then shouldn't these be named _1_7_HypotheticalChange rather than HypotheticalChange? If similar option's provided in 1.8 then it would need a different name to specify to DisableChanges?

This gets more to policy how we name these (Hypothetical)Changes but strong guidance from the start is best.


## CompatibilityOptions.Apply

Apply the set compatibility options to the runtime.

Note that CompatibilityOptions must be applied early in the process before any other Windows App
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: NOTE: `CompatibilityOptions MUST be...

  • Bold the NOTE: prefix instead of conversational english
  • Capitalize MUST, much like IEEE MUST/SHOULD/MAY phrasing (RFC 2119)

SDK APIs are called, or right after initializing the Windows App Runtime. The options must be set
early before the configuration is locked for the lifetime of the process. Since the Windows App
Runtime needs to run with a consistent configuration, it will lock the configuration when it needs
to ensure the configuration will no longer change. Calling `Apply` will also lock the
configuration.

If the configuration has already been locked, calling `Apply` will throw an `E_ILLEGAL_STATE_CHANGE`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that a way to 'Reset` or 'Unapply' the configuration? OR no, 'till process death do you part?

exception if the options differ from the locked configuration. It is okay to call `Apply` multiple
times with the same configuration, such as if the process had initialized and used the Windows
App Runtime earlier and is reinitializing for additional use.

# API Details

```c# (but really MIDL3)
namespace Microsoft.Windows.ApplicationModel.WindowsAppRuntime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing API contract

SUGGESTION:

namespace Microsoft.Windows.ApplicationModel.WindowsAppRuntime
{
    [contractversion(1)]
    apicontract AppConfigurableCompatibilityContract{};

    /// The set of servicing changes that can be disabled.
    [contract(AppConfigurableCompatibilityContract, 1)]
    enum CompatibilityChange
...

{
/// The set of servicing changes that can be disabled.
enum CompatibilityChange
{
None = 0, /// do not use this value

// Add real changes here:

// 1.7.1
// HypotheticalChange,
// OtherHypotheticalChange,
// ...
};

/// Represents a version of the Windows App Runtime.
struct WindowsAppRuntimeVersion
{
UInt32 Major;
UInt32 Minor;
UInt32 Patch;
};

/// This object is used by the app to configure any desired compatibility options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the "app" or "process"?

Can OOP WinRT servers use this API? Are OOP WinRT servers apps?

SUGGESTION: This object is used by the process...

/// for Windows App Runtime behavior of changes added in servicing updates. This
/// object is only used to set the runtime behavior and can't be used to query the
/// applied options.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no query API?

Let me guess: An app specifying this knows what it's doing so it doesn't have to query at runtime.

Question: What are class libraries supposed to do?

Lacking a query API creates issues for library authors. How does a library know current process' behavior? They don't control the process/environment they run in (not the app). Would that amount to...?

[contract(AppConfigurableCompatibilityContract, 1)]
runtimeclass CurrentCompatibilityOptions
{
    static CurrentCompatibilityOptions GetDefault();

    WindowsAppRuntimeVersion EffectiveVersion { get; }

    IVector<CompatibilityChange> DisabledChanges{ get; };
}

runtimeclass CompatibilityOptions
{
CompatibilityOptions();

/// An optional patch mode to use if the runtime version matches the major.minor version.
WindowsAppRuntimeVersion PatchMode1 { get; set; };

/// An optional patch mode to use if the runtime version matches the major.minor version.
WindowsAppRuntimeVersion PatchMode2 { get; set; };

/// An optional list of specific servicing changes to disable.
IVector<CompatibilityChange> DisabledChanges{ get; };

/// Apply the compatibility options to the runtime.
void Apply();
}
}
```