[AOT] Refactor SettingsLib/SettingsUI for Native AOT compatibility (#42644)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Key Changes:

1. Settings.UI.Library:
- Added SettingsSerializationContext.cs with comprehensive
JsonSerializable attributes for all settings types
- Updated BasePTModuleSettings.ToJsonString() to use AOT-compatible
serialization
- Updated SettingsUtils.GetFile<T>() to use AOT-compatible
deserialization
- Modified all ToString() methods in Properties classes to use
SettingsSerializationContext
- Converted struct fields to properties in SunTimes and
MouseWithoutBordersProperties for serialization compatibility

2. Settings.UI:
- Fixed namespace alias in SourceGenerationContextContext.cs to avoid
conflicts

For any future developers who discover incorrect settings resolution,
please follow up my changes to add your setting type into
JsonSerilizerContext.



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Co-authored-by: Yu Leng <yuleng@microsoft.com>
This commit is contained in:
moooyo
2025-12-02 16:31:02 +08:00
committed by GitHub
parent b075a021df
commit bcd1583bb7
38 changed files with 489 additions and 58 deletions

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.IO;
using System.IO.Abstractions;
@@ -18,27 +20,28 @@ namespace Microsoft.PowerToys.Settings.UI.Library
private const string DefaultModuleName = "";
private readonly IFile _file;
private readonly ISettingsPath _settingsPath;
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
};
private readonly JsonSerializerOptions _serializerOptions;
public SettingsUtils()
: this(new FileSystem())
{
}
public SettingsUtils(IFileSystem fileSystem)
: this(fileSystem?.File, new SettingPath(fileSystem?.Directory, fileSystem?.Path))
public SettingsUtils(IFileSystem? fileSystem, JsonSerializerOptions? serializerOptions = null)
: this(fileSystem?.File!, new SettingPath(fileSystem?.Directory, fileSystem?.Path), serializerOptions)
{
}
public SettingsUtils(IFile file, ISettingsPath settingPath)
public SettingsUtils(IFile file, ISettingsPath settingPath, JsonSerializerOptions? serializerOptions = null)
{
_file = file ?? throw new ArgumentNullException(nameof(file));
_settingsPath = settingPath;
_serializerOptions = serializerOptions ?? new JsonSerializerOptions
{
MaxDepth = 0,
IncludeFields = true,
TypeInfoResolver = SettingsSerializationContext.Default,
};
}
public bool SettingsExists(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
@@ -108,7 +111,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// This function creates a file in the powertoy folder if it does not exist and returns an object with default properties.
/// </summary>
/// <returns>Deserialized json settings object.</returns>
public T GetSettingsOrDefault<T, T2>(string powertoy = DefaultModuleName, string fileName = DefaultFileName, Func<object, object> settingsUpgrader = null)
public T GetSettingsOrDefault<T, T2>(string powertoy = DefaultModuleName, string fileName = DefaultFileName, Func<object, object>? settingsUpgrader = null)
where T : ISettingsConfig, new()
where T2 : ISettingsConfig, new()
{
@@ -128,7 +131,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
try
{
T2 oldSettings = GetSettings<T2>(powertoy, fileName);
T newSettings = (T)settingsUpgrader(oldSettings);
T newSettings = (T)settingsUpgrader!(oldSettings);
Logger.LogInfo($"Settings file {fileName} for {powertoy} was read successfully in the old format.");
// If the file needs to be modified, to save the new configurations accordingly.
@@ -156,7 +159,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
return newSettingsItem;
}
// Given the powerToy folder name and filename to be accessed, this function deserializes and returns the file.
/// <summary>
/// Deserializes settings from a JSON file.
/// </summary>
/// <typeparam name="T">The settings type to deserialize. Must be registered in <see cref="SettingsSerializationContext"/>.</typeparam>
/// <param name="powertoyFolderName">The PowerToy module folder name.</param>
/// <param name="fileName">The settings file name.</param>
/// <returns>Deserialized settings object of type T.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when type T is not registered in <see cref="SettingsSerializationContext"/>.
/// All settings types must be registered with <c>[JsonSerializable(typeof(T))]</c> attribute
/// for Native AOT compatibility.
/// </exception>
/// <remarks>
/// This method uses Native AOT-compatible JSON deserialization. Type T must be registered
/// in <see cref="SettingsSerializationContext"/> before calling this method.
/// </remarks>
private T GetFile<T>(string powertoyFolderName = DefaultModuleName, string fileName = DefaultFileName)
{
// Adding Trim('\0') to overcome possible NTFS file corruption.
@@ -165,8 +183,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
// The file itself did write the content correctly but something is off with the actual end of the file, hence the 0x00 bug
var jsonSettingsString = _file.ReadAllText(_settingsPath.GetSettingsPath(powertoyFolderName, fileName)).Trim('\0');
var options = _serializerOptions;
return JsonSerializer.Deserialize<T>(jsonSettingsString, options);
// For Native AOT compatibility, get JsonTypeInfo from the TypeInfoResolver
var typeInfo = _serializerOptions.TypeInfoResolver?.GetTypeInfo(typeof(T), _serializerOptions);
if (typeInfo == null)
{
throw new InvalidOperationException($"Type {typeof(T).FullName} is not registered in SettingsSerializationContext. Please add it to the [JsonSerializable] attributes.");
}
// Use AOT-friendly deserialization
return (T)JsonSerializer.Deserialize(jsonSettingsString, typeInfo)!;
}
// Save settings to a json file.