diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 230505b703ea2a..f64a233da042a0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -14,73 +14,73 @@ - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 https://github.com/microsoft/vstest @@ -202,9 +202,9 @@ https://github.com/dotnet/xharness e9669dc84ecd668d3bbb748758103e23b394ffef - + https://github.com/dotnet/arcade - ac8b7514ca8bcac1d071a16b7a92cb52f7058871 + 474307e526160c813c9fd58060eb8356ccca6099 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/Versions.props b/eng/Versions.props index 55bdceec246ea3..c569074e9d42b6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -40,9 +40,9 @@ - - 3.9.0 - 3.9.0 + + 4.0.0-3.final + 4.0.0-3.final @@ -52,20 +52,20 @@ 1.0.0-rc.2.21419.17 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 2.5.1-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 - 6.0.0-beta.21418.12 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 2.5.1-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 + 6.0.0-beta.21427.6 6.0.0-preview.1.102 diff --git a/eng/common/build.sh b/eng/common/build.sh index 9d3042a943e4cf..bc07a1c6848243 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -187,7 +187,10 @@ function InitializeCustomToolset { } function Build { - TryLogClientIpAddress + + if [[ "$ci" == true ]]; then + TryLogClientIpAddress + fi InitializeToolset InitializeCustomToolset diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh index 5bd205b5da3b79..3e6a8d6acf2f57 100755 --- a/eng/common/init-tools-native.sh +++ b/eng/common/init-tools-native.sh @@ -10,7 +10,7 @@ force=false download_retries=5 retry_wait_time_seconds=30 global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" -declare -A native_assets +declare -a native_assets . $scriptroot/pipeline-logging-functions.sh . $scriptroot/native/common-library.sh diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh index bf272dcf55a538..080c2c283ae468 100755 --- a/eng/common/native/common-library.sh +++ b/eng/common/native/common-library.sh @@ -148,8 +148,12 @@ function NewScriptShim { fi if [[ ! -f $tool_file_path ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" - return 1 + # try to see if the path is lower cased + tool_file_path="$(echo $tool_file_path | tr "[:upper:]" "[:lower:]")" + if [[ ! -f $tool_file_path ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" + return 1 + fi fi local shim_contents=$'#!/usr/bin/env bash\n' diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 index 3b6fc953337362..e8ab29afeb3330 100644 --- a/eng/common/post-build/sourcelink-validation.ps1 +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -107,8 +107,12 @@ $ValidatePackage = { try { $Uri = $Link -as [System.URI] - # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + if ($Link -match "submodules") { + # Skip submodule links until sourcelink properly handles submodules + $Status = 200 + } + elseif ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + # Only GitHub links are valid $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode } else { diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 7ffa3591e9ca0c..7ab9baac5c8d9a 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -83,7 +83,9 @@ try { } if ($restore) { - Try-LogClientIpAddress + if ($ci) { + Try-LogClientIpAddress + } Build 'Restore' } diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index 8669679348024c..37dceb1bab0a93 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -103,7 +103,7 @@ jobs: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - task: MicroBuildSigningPlugin@2 + - task: MicroBuildSigningPlugin@3 displayName: Install MicroBuild plugin inputs: signType: $(_SignType) diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index e607aa43697623..56ee4a577acab4 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -154,9 +154,6 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { return $global:_DotNetInstallDir } - # In case of network error, try to log the current IP for reference - Try-LogClientIpAddress - # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism $env:DOTNET_MULTILEVEL_LOOKUP=0 @@ -166,6 +163,9 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { # Disable telemetry on CI. if ($ci) { $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 + + # In case of network error, try to log the current IP for reference + Try-LogClientIpAddress } # Source Build uses DotNetCoreSdkDir variable diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 3c5f3a12c0a6ed..41e323104df468 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -402,7 +402,7 @@ function StopProcesses { function TryLogClientIpAddress () { echo 'Attempting to log this client''s IP for Azure Package feed telemetry purposes' if command -v curl > /dev/null; then - curl -s 'http://co1.msedge.net/fdv2/diagnostics.aspx' | grep ' IP: ' + curl -s 'http://co1.msedge.net/fdv2/diagnostics.aspx' | grep ' IP: ' || true fi } diff --git a/global.json b/global.json index 194c494ce541c0..103cec939e94b6 100644 --- a/global.json +++ b/global.json @@ -12,10 +12,10 @@ "python3": "3.7.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Build.Tasks.TargetFramework.Sdk": "6.0.0-beta.21418.12", - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21418.12", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21418.12", - "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21418.12", + "Microsoft.DotNet.Build.Tasks.TargetFramework.Sdk": "6.0.0-beta.21427.6", + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21427.6", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21427.6", + "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21427.6", "Microsoft.Build.NoTargets": "3.1.0", "Microsoft.Build.Traversal": "3.0.23", "Microsoft.NET.Sdk.IL": "6.0.0-rc.1.21415.6" diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj index 6990339554f497..4c3c0f82a1a55b 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj @@ -64,6 +64,18 @@ + + + <_Crossgen2SymbolFilesToPackage Include="@(Reference->'$(CoreCLRArtifactsPath)PDB\%(FileName).pdb')" /> + + <_Crossgen2SymbolFilesToPackage Include="@(NativeRuntimeAsset->'$(CoreCLRArtifactsPdbDir)%(FileName).pdb')" Condition="'$(TargetOS)' == 'windows' and '%(FileName)' != 'crossgen2'" /> + <_Crossgen2SymbolFilesToPackage Include="@(NativeRuntimeAsset->'$(CoreCLRArtifactsPath)%(FileName)%(Extension)$(SymbolsSuffix)')" Condition="'$(TargetOS)' != 'windows' and '%(FileName)' != 'crossgen2'" /> + <_Crossgen2SymbolFilesToPackage Remove="@(_Crossgen2SymbolFilesToPackage)" Condition="!Exists('%(Identity)')" /> + + <_SymbolFilesToPackage Include="@(_Crossgen2SymbolFilesToPackage)" TargetPath="tools/" /> + + + <_diaSymTargetArch>$(TargetArchitecture) diff --git a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.iOS.cs b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.iOS.cs new file mode 100644 index 00000000000000..8d73184ca4d50a --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.iOS.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SearchPath_TempDirectory")] + internal static extern string SearchPathTempDirectory(); + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs index 3bcb9162d57bfc..123ac9235b9fdc 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs @@ -14,24 +14,29 @@ internal static partial class Kernel32 internal const uint SYMLINK_FLAG_RELATIVE = 1; // https://msdn.microsoft.com/library/windows/hardware/ff552012.aspx - // We don't need all the struct fields; omitting the rest. [StructLayout(LayoutKind.Sequential)] - internal unsafe struct REPARSE_DATA_BUFFER + internal unsafe struct SymbolicLinkReparseBuffer { internal uint ReparseTag; internal ushort ReparseDataLength; internal ushort Reserved; - internal SymbolicLinkReparseBuffer ReparseBufferSymbolicLink; + internal ushort SubstituteNameOffset; + internal ushort SubstituteNameLength; + internal ushort PrintNameOffset; + internal ushort PrintNameLength; + internal uint Flags; + } - [StructLayout(LayoutKind.Sequential)] - internal struct SymbolicLinkReparseBuffer - { - internal ushort SubstituteNameOffset; - internal ushort SubstituteNameLength; - internal ushort PrintNameOffset; - internal ushort PrintNameLength; - internal uint Flags; - } + [StructLayout(LayoutKind.Sequential)] + internal struct MountPointReparseBuffer + { + public uint ReparseTag; + public ushort ReparseDataLength; + public ushort Reserved; + public ushort SubstituteNameOffset; + public ushort SubstituteNameLength; + public ushort PrintNameOffset; + public ushort PrintNameLength; } } } diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs index 4928270b06f170..1c57260b8601de 100644 --- a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs +++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs @@ -141,10 +141,9 @@ public static TextSpan MakeSpan(string text, int spanNum) /// Runs a Roslyn generator over a set of source files. /// public static async Task<(ImmutableArray, ImmutableArray)> RunGenerator( - ISourceGenerator generator, + IIncrementalGenerator generator, IEnumerable? references, IEnumerable sources, - AnalyzerConfigOptionsProvider? optionsProvider = null, bool includeBaseReferences = true, CancellationToken cancellationToken = default) { @@ -156,7 +155,9 @@ public static TextSpan MakeSpan(string text, int spanNum) Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); - CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }, optionsProvider: optionsProvider); + // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change. + CSharpParseOptions options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); + CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: options); GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); GeneratorDriverRunResult r = gd.GetRunResult(); diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index efef067dbce645..fbd5df9afdde75 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -267,6 +267,28 @@ private static bool GetStaticNonPublicBooleanPropertyValue(string typeName, stri public static bool IsIcuGlobalization => ICUVersion > new Version(0,0,0,0); public static bool IsNlsGlobalization => IsNotInvariantGlobalization && !IsIcuGlobalization; + public static bool IsSubstAvailable + { + get + { + try + { + if (IsWindows) + { + string systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); + if (string.IsNullOrWhiteSpace(systemRoot)) + { + return false; + } + string system32 = Path.Combine(systemRoot, "System32"); + return File.Exists(Path.Combine(system32, "subst.exe")); + } + } + catch { } + return false; + } + } + private static Version GetICUVersion() { int version = 0; diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs index 4a995039104b9d..b448bc79081e8d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -17,6 +17,8 @@ public partial class LoggerMessageGenerator { internal class Parser { + private const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; + private readonly CancellationToken _cancellationToken; private readonly Compilation _compilation; private readonly Action _reportDiagnostic; @@ -28,13 +30,41 @@ public Parser(Compilation compilation, Action reportDiagnostic, Canc _reportDiagnostic = reportDiagnostic; } + internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => + node is MethodDeclarationSyntax m && m.AttributeLists.Count > 0; + + internal static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + { + var methodDeclarationSyntax = (MethodDeclarationSyntax)context.Node; + + foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + IMethodSymbol attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; + if (attributeSymbol == null) + { + continue; + } + + INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; + string fullName = attributeContainingTypeSymbol.ToDisplayString(); + + if (fullName == LoggerMessageAttribute) + { + return methodDeclarationSyntax.Parent as ClassDeclarationSyntax; + } + } + } + + return null; + } + /// /// Gets the set of logging classes containing methods to output. /// public IReadOnlyList GetLogClasses(IEnumerable classes) { - const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; - INamedTypeSymbol loggerMessageAttribute = _compilation.GetTypeByMetadataName(LoggerMessageAttribute); if (loggerMessageAttribute == null) { @@ -442,11 +472,11 @@ public IReadOnlyList GetLogClasses(IEnumerable + bool IsAllowedKind(SyntaxKind kind) => kind == SyntaxKind.ClassDeclaration || kind == SyntaxKind.StructDeclaration || kind == SyntaxKind.RecordDeclaration; - + while (parentLoggerClass != null && IsAllowedKind(parentLoggerClass.Kind())) { currentLoggerClass.ParentClass = new LoggerClass diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs index 92105d515e328c..7aaca68e758a28 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Microsoft.CodeAnalysis; @@ -15,50 +18,38 @@ namespace Microsoft.Extensions.Logging.Generators { [Generator] - public partial class LoggerMessageGenerator : ISourceGenerator + public partial class LoggerMessageGenerator : IIncrementalGenerator { - [ExcludeFromCodeCoverage] - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(SyntaxReceiver.Create); + IncrementalValuesProvider classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (ctx, _) => Parser.GetSemanticTargetForGeneration(ctx)) + .Where(static m => m is not null); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = + context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Item1, source.Item2, spc)); } - [ExcludeFromCodeCoverage] - public void Execute(GeneratorExecutionContext context) + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) { - if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.ClassDeclarations.Count == 0) + if (classes.IsDefaultOrEmpty) { // nothing to do yet return; } - var p = new Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); - IReadOnlyList logClasses = p.GetLogClasses(receiver.ClassDeclarations); + IEnumerable distinctClasses = classes.Distinct(); + + var p = new Parser(compilation, context.ReportDiagnostic, context.CancellationToken); + IReadOnlyList logClasses = p.GetLogClasses(distinctClasses); if (logClasses.Count > 0) { var e = new Emitter(); string result = e.Emit(logClasses, context.CancellationToken); - - context.AddSource("LoggerMessage.g.cs", SourceText.From(result, Encoding.UTF8)); - } - } - [ExcludeFromCodeCoverage] - private sealed class SyntaxReceiver : ISyntaxReceiver - { - internal static ISyntaxReceiver Create() - { - return new SyntaxReceiver(); - } - - public List ClassDeclarations { get; } = new (); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax classSyntax) - { - ClassDeclarations.Add(classSyntax); - } + context.AddSource("LoggerMessage.g.cs", SourceText.From(result, Encoding.UTF8)); } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs index b2fe61084c52ea..a1ec4253469ae7 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs @@ -380,6 +380,7 @@ public class Object {} public class Void {} public class String {} public struct DateTime {} + public abstract class Attribute {} } namespace System.Collections { @@ -392,10 +393,12 @@ public interface ILogger {} } namespace Microsoft.Extensions.Logging { - public class LoggerMessageAttribute {} + public class LoggerMessageAttribute : System.Attribute {} } partial class C { + [Microsoft.Extensions.Logging.LoggerMessage] + public static partial void Log(ILogger logger); } ", false, includeBaseReferences: false, includeLoggingReferences: false); diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index c5f15e1e9d1d6f..721be5eee63deb 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -219,6 +219,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_GetOSArchitecture) DllImportEntry(SystemNative_GetProcessArchitecture) DllImportEntry(SystemNative_SearchPath) + DllImportEntry(SystemNative_SearchPath_TempDirectory) DllImportEntry(SystemNative_RegisterForSigChld) DllImportEntry(SystemNative_SetDelayedSigChildConsoleConfigurationHandler) DllImportEntry(SystemNative_SetTerminalInvalidationHandler) diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.c b/src/libraries/Native/Unix/System.Native/pal_searchpath.c index fbf3d903df01c7..de8037dc1a2959 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.c +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.c @@ -10,3 +10,9 @@ const char* SystemNative_SearchPath(int32_t folderId) __builtin_unreachable(); return NULL; } + +const char* SystemNative_SearchPath_TempDirectory() +{ + __builtin_unreachable(); + return NULL; +} diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.h b/src/libraries/Native/Unix/System.Native/pal_searchpath.h index e2de05d8c8860a..cdb872914624ca 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.h +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.h @@ -7,3 +7,5 @@ #include "pal_types.h" PALEXPORT const char* SystemNative_SearchPath(int32_t folderId); + +PALEXPORT const char* SystemNative_SearchPath_TempDirectory(void); diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.m b/src/libraries/Native/Unix/System.Native/pal_searchpath.m index 2e96041f3dda82..231c508c527cab 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.m +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.m @@ -11,3 +11,10 @@ const char* path = [[url path] UTF8String]; return path == NULL ? NULL : strdup (path); } + +const char* SystemNative_SearchPath_TempDirectory() +{ + NSString* tempPath = NSTemporaryDirectory(); + const char *path = [tempPath UTF8String]; + return path == NULL ? NULL : strdup (path); +} diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c index 2d66847c1731b3..0770a946bee1ed 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c @@ -596,19 +596,19 @@ int32_t AppleCryptoNative_SslSetEnabledCipherSuites(SSLContextRef sslContext, co // Max numCipherSuites is 2^16 (all possible cipher suites) assert(numCipherSuites < (1 << 16)); -#if !defined(TARGET_MACCATALYST) && !defined(TARGET_IOS) && !defined(TARGET_TVOS) +#if !defined(TARGET_ARM64) && !defined(TARGET_IOS) && !defined(TARGET_TVOS) if (sizeof(SSLCipherSuite) == sizeof(uint32_t)) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - // macOS + // macOS & MacCatalyst x64 return SSLSetEnabledCiphers(sslContext, (const SSLCipherSuite *)cipherSuites, (size_t)numCipherSuites); #pragma clang diagnostic pop } else #endif { - // MacCatalyst, iOS, tvOS, watchOS + // MacCatalyst arm64, iOS, tvOS, watchOS SSLCipherSuite* cipherSuites16 = (SSLCipherSuite*)calloc((size_t)numCipherSuites, sizeof(SSLCipherSuite)); if (cipherSuites16 == NULL) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index 0682f8ce16e5bb..9f61e73a36a6b1 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs @@ -460,11 +460,10 @@ private void ResolveLinkTarget_ReturnFinalTarget(string link1Path, string link1T Assert.Equal(filePath, finalTarget.FullName); } + // Must call inside a remote executor protected void CreateSymbolicLink_PathToTarget_RelativeToLinkPath_Internal(bool createOpposite) { - string tempCwd = GetRandomDirPath(); - Directory.CreateDirectory(tempCwd); - Directory.SetCurrentDirectory(tempCwd); + string tempCwd = ChangeCurrentDirectory(); // Create a dummy file or directory in cwd. string fileOrDirectoryInCwd = GetRandomFileName(); diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index f120cdf10eba53..6e5637d8327271 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -1,10 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; using Xunit; namespace System.IO.Tests @@ -36,5 +32,18 @@ protected DirectoryInfo CreateSelfReferencingSymbolicLink() protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName()); private Lazy ActualTestDirectory => new Lazy(() => GetTestDirectoryActualCasing()); + + /// + /// Changes the current working directory path to a new temporary directory. + /// Important: Make sure to call this inside a remote executor to avoid changing the cwd for all tests in same process. + /// + /// The path of the new cwd. + protected string ChangeCurrentDirectory() + { + string tempCwd = GetRandomDirPath(); + Directory.CreateDirectory(tempCwd); + Directory.SetCurrentDirectory(tempCwd); + return tempCwd; + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs new file mode 100644 index 00000000000000..f4f2150b803149 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.IO.Tests +{ + [PlatformSpecific(TestPlatforms.Windows)] + public class Junctions : BaseSymbolicLinks + { + protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) + { + Assert.True(MountHelper.CreateJunction(junctionPath, targetPath)); + DirectoryInfo junctionInfo = new(junctionPath); + return junctionInfo; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Junction_ResolveLinkTarget(bool returnFinalTarget) + { + string junctionPath = GetRandomLinkPath(); + string targetPath = GetRandomDirPath(); + + Directory.CreateDirectory(targetPath); + DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); + + FileSystemInfo? targetFromDirectoryInfo = junctionInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetFromDirectory = Directory.ResolveLinkTarget(junctionPath, returnFinalTarget); + + Assert.True(targetFromDirectoryInfo is DirectoryInfo); + Assert.True(targetFromDirectory is DirectoryInfo); + + Assert.Equal(targetPath, junctionInfo.LinkTarget); + + Assert.Equal(targetPath, targetFromDirectoryInfo.FullName); + Assert.Equal(targetPath, targetFromDirectory.FullName); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Junction_ResolveLinkTarget_WithIndirection(bool returnFinalTarget) + { + string firstJunctionPath = GetRandomLinkPath(); + string middleJunctionPath = GetRandomLinkPath(); + string targetPath = GetRandomDirPath(); + + Directory.CreateDirectory(targetPath); + CreateJunction(middleJunctionPath, targetPath); + DirectoryInfo firstJunctionInfo = CreateJunction(firstJunctionPath, middleJunctionPath); + + string expectedTargetPath = returnFinalTarget ? targetPath : middleJunctionPath; + + FileSystemInfo? targetFromDirectoryInfo = firstJunctionInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetFromDirectory = Directory.ResolveLinkTarget(firstJunctionPath, returnFinalTarget); + + Assert.True(targetFromDirectoryInfo is DirectoryInfo); + Assert.True(targetFromDirectory is DirectoryInfo); + + // Always the immediate target + Assert.Equal(middleJunctionPath, firstJunctionInfo.LinkTarget); + + Assert.Equal(expectedTargetPath, targetFromDirectoryInfo.FullName); + Assert.Equal(expectedTargetPath, targetFromDirectory.FullName); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index 5423bda6814b3b..8c7f3608d35ce2 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -10,14 +10,14 @@ #define DEBUG using System; -using System.IO; -using System.Text; -using System.Diagnostics; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using System.Runtime.InteropServices; -using System.ComponentModel; -using System.Threading; +using System.Text; using System.Threading.Tasks; + public static class MountHelper { [DllImport("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] @@ -28,9 +28,7 @@ public static class MountHelper [DllImport("kernel32.dll", EntryPoint = "DeleteVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] private static extern bool DeleteVolumeMountPoint(string mountPoint); - /// Creates a symbolic link using command line tools - /// The existing file - /// + /// Creates a symbolic link using command line tools. public static bool CreateSymbolicLink(string linkPath, string targetPath, bool isDirectory) { Process symLinkProcess = new Process(); @@ -48,20 +46,78 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i symLinkProcess.StartInfo.RedirectStandardOutput = true; symLinkProcess.Start(); - if (symLinkProcess != null) + symLinkProcess.WaitForExit(); + return symLinkProcess.ExitCode == 0; + } + + /// On Windows, creates a junction using command line tools. + public static bool CreateJunction(string junctionPath, string targetPath) + { + if (!OperatingSystem.IsWindows()) { - symLinkProcess.WaitForExit(); - return (0 == symLinkProcess.ExitCode); + throw new PlatformNotSupportedException(); } - else + + return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath)); + } + + /// + /// On Windows, mounts a folder to an assigned virtual drive letter using the subst command. + /// subst is not available in Windows Nano. + /// + public static char CreateVirtualDrive(string targetDir) + { + if (!OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException(); + } + + char driveLetter = GetNextAvailableDriveLetter(); + bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir)); + if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) + { + throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); + } + return driveLetter; + + // Finds the next unused drive letter and returns it. + char GetNextAvailableDriveLetter() { - return false; + List existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList(); + + // A,B are reserved, C is usually reserved + IEnumerable range = Enumerable.Range('D', 'Z' - 'D'); + IEnumerable castRange = range.Select(x => Convert.ToChar(x)); + IEnumerable allDrivesLetters = castRange.Except(existingDrives); + + if (!allDrivesLetters.Any()) + { + throw new ArgumentOutOfRangeException("No drive letters available"); + } + + return allDrivesLetters.First(); } } - public static void Mount(string volumeName, string mountPoint) + /// + /// On Windows, unassigns the specified virtual drive letter from its mounted folder. + /// + public static void DeleteVirtualDrive(char driveLetter) { + if (!OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException(); + } + bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:")); + if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) + { + throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); + } + } + + public static void Mount(string volumeName, string mountPoint) + { if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar) volumeName += Path.DirectorySeparatorChar; if (mountPoint[mountPoint.Length - 1] != Path.DirectorySeparatorChar) @@ -93,8 +149,47 @@ public static void Unmount(string mountPoint) throw new Exception(string.Format("Win32 error: {0}", Marshal.GetLastPInvokeError())); } + private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments) + { + var info = new ProcessStartInfo + { + FileName = fileName, + UseShellExecute = false, + RedirectStandardOutput = true + }; + + foreach (var argument in arguments) + { + info.ArgumentList.Add(argument); + } + + return info; + } + + private static bool RunProcess(ProcessStartInfo startInfo) + { + var process = Process.Start(startInfo); + process.WaitForExit(); + return process.ExitCode == 0; + } + + private static string SubstPath + { + get + { + if (!OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException(); + } + + string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows"; + string system32 = Path.Join(systemRoot, "System32"); + return Path.Join(system32, "subst.exe"); + } + } + /// For standalone debugging help. Change Main0 to Main - public static void Main0(string[] args) + public static void Main0(string[] args) { try { diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index 736d3b1ee5654b..bb7cac297d73a3 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -1,4 +1,4 @@ - + true true @@ -80,9 +80,11 @@ + + @@ -211,8 +213,7 @@ - + diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs new file mode 100644 index 00000000000000..d88425086b7024 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -0,0 +1,247 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Tests +{ + // Need to reuse the same virtual drive for all the test methods. + // Creating and disposing one virtual drive per class achieves this. + [PlatformSpecific(TestPlatforms.Windows)] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))] + public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks + { + protected override void Dispose(bool disposing) + { + try + { + if (VirtualDriveLetter != default) + { + MountHelper.DeleteVirtualDrive(VirtualDriveLetter); + Directory.Delete(VirtualDriveTargetDir, recursive: true); + } + } + catch { } // avoid exceptions on dispose + base.Dispose(disposing); + } + + [Theory] + // false, false, false, false // Target is not in virtual drive + // false, false, true, false // Target is not in virtual drive + [InlineData(false, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, false)] // Final target expected, target is in virtual drive + // true, false, false, false // Target is not in virtual drive + // true, false, true, false // Target is not in virtual drive + [InlineData(true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, false)] // Final target expected, target is in virtual drive + public void VirtualDrive_SymbolicLinks_LinkAndTarget( + bool isLinkInVirtualDrive, + bool isTargetInVirtualDrive, + bool returnFinalTarget, + bool isExpectedTargetPathVirtual) + { + string linkExpectedFolderPath = GetVirtualOrRealPath(isLinkInVirtualDrive); + // File link + string fileLinkName = GetRandomLinkName(); + string fileLinkPath = Path.Join(linkExpectedFolderPath, fileLinkName); + // Directory link + string dirLinkName = GetRandomLinkName(); + string dirLinkPath = Path.Join(linkExpectedFolderPath, dirLinkName); + + string targetExpectedFolderPath = GetVirtualOrRealPath(isTargetInVirtualDrive); + // File target + string fileTargetFileName = GetRandomFileName(); + string fileTargetPath = Path.Join(targetExpectedFolderPath, fileTargetFileName); + // Directory target + string dirTargetFileName = GetRandomDirName(); + string dirTargetPath = Path.Join(targetExpectedFolderPath, dirTargetFileName); + + // Create targets + File.Create(fileTargetPath).Dispose(); + Directory.CreateDirectory(dirTargetPath); + + // Create links + FileInfo fileLinkInfo = new FileInfo(fileLinkPath); + fileLinkInfo.CreateAsSymbolicLink(fileTargetPath); + DirectoryInfo dirLinkInfo = new DirectoryInfo(dirLinkPath); + dirLinkInfo.CreateAsSymbolicLink(dirTargetPath); + + // The expected results depend on the target location and the value of returnFinalTarget + + // LinkTarget always retrieves the immediate target, so the expected value + // is always the path that was provided by the user for the target + + // Verify the LinkTarget values of the link infos + Assert.Equal(fileTargetPath, fileLinkInfo.LinkTarget); + Assert.Equal(dirTargetPath, dirLinkInfo.LinkTarget); + + // When the target is in a virtual drive, and returnFinalTarget is true, + // the expected target path is the real path, not the virtual path + string expectedTargetPath = GetVirtualOrRealPath(isExpectedTargetPathVirtual); + + string expectedTargetFileInfoFullName = Path.Join(expectedTargetPath, fileTargetFileName); + string expectedTargetDirectoryInfoFullName = Path.Join(expectedTargetPath, dirTargetFileName); + + // Verify target infos from link info instances + FileSystemInfo? targetFileInfoFromFileInfoLink = fileLinkInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetDirInfoFromDirInfoLink = dirLinkInfo.ResolveLinkTarget(returnFinalTarget); + + Assert.True(targetFileInfoFromFileInfoLink is FileInfo); + Assert.True(targetDirInfoFromDirInfoLink is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFileInfoLink.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetDirInfoFromDirInfoLink.FullName); + + // Verify targets infos via static methods + FileSystemInfo? targetFileInfoFromFile = File.ResolveLinkTarget(fileLinkPath, returnFinalTarget); + FileSystemInfo? targetFileInfoFromDirectory = Directory.ResolveLinkTarget(dirLinkPath, returnFinalTarget); + + Assert.True(targetFileInfoFromFile is FileInfo); + Assert.True(targetFileInfoFromDirectory is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFile.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName); + } + + + [Theory] + // false, false, false, false, false // Target is not in virtual drive + // false, false, false, true, false // Target is not in virtual drive + [InlineData(false, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(false, false, true, true, false)] // Final target expected, target is in virtual drive + // false, true, false, false, false // Target is not in virtual drive + // false, true, false, true, false // Target is not in virtual drive + [InlineData(false, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, true, false)] // Final target expected, target is in virtual drive + // true, false, false, false, false // Target is not in virtual drive + // true, false, false, true, false // Target is not in virtual drive + [InlineData(true, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(true, false, true, true, false)] // Final target expected, target is in virtual drive + // true, true, false, false, false // Target is not in virtual drive + // true, true, false, true, false // Target is not in virtual drive + [InlineData(true, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, true, false)] // Final target expected, target is in virtual drive + public void VirtualDrive_SymbolicLinks_WithIndirection( + bool isFirstLinkInVirtualDrive, + bool isMiddleLinkInVirtualDrive, + bool isTargetInVirtualDrive, + bool returnFinalTarget, + bool isExpectedTargetPathVirtual) + { + string firstLinkExpectedFolderPath = GetVirtualOrRealPath(isFirstLinkInVirtualDrive); + // File link + string fileLinkPath = Path.Join(firstLinkExpectedFolderPath, GetRandomLinkName()); + // Directory link + string dirLinkPath = Path.Join(firstLinkExpectedFolderPath, GetRandomLinkName()); + + string middleLinkExpectedFolderPath = GetVirtualOrRealPath(isMiddleLinkInVirtualDrive); + // File middle link + string fileMiddleLinkFileName = GetRandomLinkName(); + string fileMiddleLinkPath = Path.Join(middleLinkExpectedFolderPath, fileMiddleLinkFileName); + // Directory middle link + string dirMiddleLinkFileName = GetRandomLinkName(); + string dirMiddleLinkPath = Path.Join(middleLinkExpectedFolderPath, dirMiddleLinkFileName); + + string targetExpectedFolderPath = GetVirtualOrRealPath(isTargetInVirtualDrive); + // File final target + string fileFinalTargetFileName = GetRandomFileName(); + string fileFinalTargetPath = Path.Join(targetExpectedFolderPath, fileFinalTargetFileName); + // Directory final target + string dirFinalTargetFileName = GetRandomDirName(); + string dirFinalTargetPath = Path.Join(targetExpectedFolderPath, dirFinalTargetFileName); + + // Create targets + File.Create(fileFinalTargetPath).Dispose(); + Directory.CreateDirectory(dirFinalTargetPath); + + // Create initial links + FileInfo fileLinkInfo = new FileInfo(fileLinkPath); + fileLinkInfo.CreateAsSymbolicLink(fileMiddleLinkPath); + + DirectoryInfo dirLinkInfo = new DirectoryInfo(dirLinkPath); + dirLinkInfo.CreateAsSymbolicLink(dirMiddleLinkPath); + + // Create middle links + FileInfo fileMiddleLinkInfo = new FileInfo(fileMiddleLinkPath); + fileMiddleLinkInfo.CreateAsSymbolicLink(fileFinalTargetPath); + + DirectoryInfo dirMiddleLinkInfo = new DirectoryInfo(dirMiddleLinkPath); + dirMiddleLinkInfo.CreateAsSymbolicLink(dirFinalTargetPath); + + // The expected results depend on the target location and the value of returnFinalTarget + + // LinkTarget always retrieves the immediate target, so the expected value + // is always the path that was provided by the user for the middle link + + // Verify the LinkTarget values of the link infos + Assert.Equal(fileMiddleLinkPath, fileLinkInfo.LinkTarget); + Assert.Equal(dirMiddleLinkPath, dirLinkInfo.LinkTarget); + + // When the target is in a virtual drive, + // the expected target path is the real path, not the virtual path + // When returnFinalTarget is true, the expected target path is the + // resolved path from the final target in the chain of links + string expectedTargetPath = GetVirtualOrRealPath(isExpectedTargetPathVirtual); + + string expectedTargetFileInfoFullName = Path.Join(expectedTargetPath, + returnFinalTarget ? fileFinalTargetFileName : fileMiddleLinkFileName); + + string expectedTargetDirectoryInfoFullName = Path.Join(expectedTargetPath, + returnFinalTarget ? dirFinalTargetFileName : dirMiddleLinkFileName); + + // Verify target infos from link info instances + FileSystemInfo? targetFileInfoFromFileInfoLink = fileLinkInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetDirInfoFromDirInfoLink = dirLinkInfo.ResolveLinkTarget(returnFinalTarget); + + Assert.True(targetFileInfoFromFileInfoLink is FileInfo); + Assert.True(targetDirInfoFromDirInfoLink is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFileInfoLink.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetDirInfoFromDirInfoLink.FullName); + + // Verify targets infos via static methods + FileSystemInfo? targetFileInfoFromFile = File.ResolveLinkTarget(fileLinkPath, returnFinalTarget); + FileSystemInfo? targetFileInfoFromDirectory = Directory.ResolveLinkTarget(dirLinkPath, returnFinalTarget); + + Assert.True(targetFileInfoFromFile is FileInfo); + Assert.True(targetFileInfoFromDirectory is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFile.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName); + } + + private string GetVirtualOrRealPath(bool condition) => condition ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir; + + // Temporary Windows directory that can be mounted to a drive letter using the subst command + private string? _virtualDriveTargetDir = null; + private string VirtualDriveTargetDir + { + get + { + if (_virtualDriveTargetDir == null) + { + // Create a folder inside the temp directory so that it can be mounted to a drive letter with subst + _virtualDriveTargetDir = Path.Join(Path.GetTempPath(), GetRandomDirName()); + Directory.CreateDirectory(_virtualDriveTargetDir); + } + + return _virtualDriveTargetDir; + } + } + + // Windows drive letter that points to a mounted directory using the subst command + private char _virtualDriveLetter = default; + private char VirtualDriveLetter + { + get + { + if (_virtualDriveLetter == default) + { + // Mount the folder to a drive letter + _virtualDriveLetter = MountHelper.CreateVirtualDrive(VirtualDriveTargetDir); + } + return _virtualDriveLetter; + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 5a1593f032c69a..e72c543d580803 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2112,6 +2112,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index ee7c38e7777cfd..931e4b62e5a178 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -733,9 +733,9 @@ internal static void ValidateParseStyleInteger(NumberStyles style) if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0 && (style & ~NumberStyles.HexNumber) != 0) { - throwInvalid(style); + ThrowInvalid(style); - void throwInvalid(NumberStyles value) + static void ThrowInvalid(NumberStyles value) { if ((value & InvalidNumberStyles) != 0) { @@ -752,9 +752,9 @@ internal static void ValidateParseStyleFloatingPoint(NumberStyles style) // Check for undefined flags or hex number if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0) { - throwInvalid(style); + ThrowInvalid(style); - void throwInvalid(NumberStyles value) + static void ThrowInvalid(NumberStyles value) { if ((value & InvalidNumberStyles) != 0) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index b92de0ad38ae9b..d2bde47c3134b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -499,32 +499,48 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i } Span bufferSpan = new(buffer); - success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.REPARSE_DATA_BUFFER rdb); + success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink); Debug.Assert(success); - // Only symbolic links are supported at the moment. - if ((rdb.ReparseTag & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) - { - return null; - } - - // We use PrintName instead of SubstitutneName given that we don't want to return a NT path when the link wasn't created with such NT path. + // We use PrintName(Offset|Length) instead of SubstituteName(Offset|Length) given that we don't want to return + // an NT path when the link wasn't created with such NT path. // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. - // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. e.g: mklink /D linkName \??\C:\path\to\target. - int printNameNameOffset = sizeof(Interop.Kernel32.REPARSE_DATA_BUFFER) + rdb.ReparseBufferSymbolicLink.PrintNameOffset; - int printNameNameLength = rdb.ReparseBufferSymbolicLink.PrintNameLength; + // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. + // e.g: mklink /D linkName \??\C:\path\to\target. + + if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) + { + int printNameOffset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.PrintNameOffset; + int printNameLength = rbSymlink.PrintNameLength; + + Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); + Debug.Assert((rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPath)); - Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); - Debug.Assert((rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPath)); + if (returnFullPath && (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) + { + // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. + return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath); + } - if (returnFullPath && (rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) + return targetPath.ToString(); + } + else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { - // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. - return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath); + success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint); + Debug.Assert(success); + + int printNameOffset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.PrintNameOffset; + int printNameLength = rbMountPoint.PrintNameLength; + + Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); + + // Unlike symlinks, mount point paths cannot be relative + Debug.Assert(!PathInternal.IsPartiallyQualified(targetPath)); + return targetPath.ToString(); } - return targetPath.ToString(); + return null; } finally { @@ -539,8 +555,9 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i // The file or directory is not a reparse point. if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 || - // Only symbolic links are supported at the moment. - (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) + // Only symbolic links and mount points are supported at the moment. + ((data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0 && + (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) == 0)) { return null; } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.NoniOS.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.NoniOS.cs new file mode 100644 index 00000000000000..b2a9a4b06a083e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.NoniOS.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO +{ + public static partial class Path + { + private static string DefaultTempPath => "/tmp/"; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs index 61786ad757a434..f3ec19941c21b2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs @@ -80,7 +80,6 @@ private static string RemoveLongPathPrefix(string path) public static string GetTempPath() { const string TempEnvVar = "TMPDIR"; - const string DefaultTempPath = "/tmp/"; // Get the temp path from the TMPDIR environment variable. // If it's not set, just return the default path. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.iOS.cs new file mode 100644 index 00000000000000..c1c4a7047eec5e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.iOS.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace System.IO +{ + public static partial class Path + { + private static string? s_defaultTempPath; + + private static string DefaultTempPath => + s_defaultTempPath ?? (s_defaultTempPath = Interop.Sys.SearchPathTempDirectory()) ?? + throw new InvalidOperationException(); + } +} diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index f019b1eb11a02f..f2b35eb6c2cdf7 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -330,6 +330,13 @@ public void FailFast_ExceptionStackTrace_InnerException() } } + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix | TestPlatforms.Browser)] + public void GetFolderPath_Unix_PersonalExists() + { + Assert.True(Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Personal))); + } + [Fact] [PlatformSpecific(TestPlatforms.AnyUnix | TestPlatforms.Browser)] // Tests OS-specific environment public void GetFolderPath_Unix_PersonalIsHomeAndUserProfile() @@ -339,7 +346,11 @@ public void GetFolderPath_Unix_PersonalIsHomeAndUserProfile() Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.Personal)); Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); } - Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + // tvOS effectively doesn't have a HOME + if (!PlatformDetection.IstvOS) + { + Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + } } [Theory] diff --git a/src/libraries/System.Text.Json/System.Text.Json.sln b/src/libraries/System.Text.Json/System.Text.Json.sln index b8309363a7b30a..42d0da090e9bc4 100644 --- a/src/libraries/System.Text.Json/System.Text.Json.sln +++ b/src/libraries/System.Text.Json/System.Text.Json.sln @@ -43,7 +43,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Encodings.Web", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections.Immutable", "..\System.Collections.Immutable\ref\System.Collections.Immutable.csproj", "{BE27618A-2916-4269-9AD5-6BC5EDC32B30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Unit.Tests", "tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Unit.Tests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 5acb94ea9d34df..ca93f97e70585e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -79,15 +79,17 @@ private sealed partial class Emitter defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); - private readonly GeneratorExecutionContext _executionContext; + private readonly SourceProductionContext _sourceProductionContext; private ContextGenerationSpec _currentContext = null!; private readonly SourceGenerationSpec _generationSpec = null!; - public Emitter(in GeneratorExecutionContext executionContext, SourceGenerationSpec generationSpec) + private readonly HashSet _emittedPropertyFileNames = new(); + + public Emitter(in SourceProductionContext sourceProductionContext, SourceGenerationSpec generationSpec) { - _executionContext = executionContext; + _sourceProductionContext = sourceProductionContext; _generationSpec = generationSpec; } @@ -166,7 +168,7 @@ namespace {@namespace} sb.AppendLine("}"); } - _executionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8)); + _sourceProductionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8)); } private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) @@ -243,7 +245,7 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) break; case ClassType.TypeUnsupportedBySourceGen: { - _executionContext.ReportDiagnostic( + _sourceProductionContext.ReportDiagnostic( Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeGenerationSpec.TypeRef })); return; } @@ -253,13 +255,16 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) } } - try + // Don't add a duplicate file, but instead raise a diagnostic to say the duplicate has been skipped. + // Workaround https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. + string propertyFileName = $"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs"; + if (_emittedPropertyFileNames.Add(propertyFileName)) { - AddSource($"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", source); + AddSource(propertyFileName, source); } - catch (ArgumentException) + else { - _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName })); + _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName })); } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index b75b7a686bb4cf..cd47bcae0c0755 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -31,7 +32,8 @@ private sealed class Parser private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute"; - private readonly GeneratorExecutionContext _executionContext; + private readonly Compilation _compilation; + private readonly SourceProductionContext _sourceProductionContext; private readonly MetadataLoadContextInternal _metadataLoadContext; private readonly Type _ilistOfTType; @@ -43,7 +45,7 @@ private sealed class Parser private readonly Type? _dictionaryType; private readonly Type? _idictionaryOfTKeyTValueType; private readonly Type? _ireadonlyDictionaryType; - private readonly Type? _isetType; + private readonly Type? _isetType; private readonly Type? _stackOfTType; private readonly Type? _queueOfTType; private readonly Type? _concurrentStackType; @@ -96,10 +98,11 @@ private sealed class Parser defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public Parser(in GeneratorExecutionContext executionContext) + public Parser(Compilation compilation, in SourceProductionContext sourceProductionContext) { - _executionContext = executionContext; - _metadataLoadContext = new MetadataLoadContextInternal(executionContext.Compilation); + _compilation = compilation; + _sourceProductionContext = sourceProductionContext; + _metadataLoadContext = new MetadataLoadContextInternal(_compilation); _ilistOfTType = _metadataLoadContext.Resolve(SpecialType.System_Collections_Generic_IList_T); _icollectionOfTType = _metadataLoadContext.Resolve(SpecialType.System_Collections_Generic_ICollection_T); @@ -138,9 +141,9 @@ public Parser(in GeneratorExecutionContext executionContext) PopulateKnownTypes(); } - public SourceGenerationSpec? GetGenerationSpec(List classDeclarationSyntaxList) + public SourceGenerationSpec? GetGenerationSpec(ImmutableArray classDeclarationSyntaxList) { - Compilation compilation = _executionContext.Compilation; + Compilation compilation = _compilation; INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute"); @@ -198,7 +201,7 @@ public Parser(in GeneratorExecutionContext executionContext) if (!TryGetClassDeclarationList(contextTypeSymbol, out List classDeclarationList)) { // Class or one of its containing types is not partial so we can't add to it. - _executionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name })); + _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name })); continue; } @@ -400,6 +403,36 @@ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [Not return typeGenerationSpec; } + internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 }, BaseList: { Types : {Count : > 0 } } }; + + internal static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + { + var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; + + foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + IMethodSymbol attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol; + if (attributeSymbol == null) + { + continue; + } + + INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType; + string fullName = attributeContainingTypeSymbol.ToDisplayString(); + + if (fullName == "System.Text.Json.Serialization.JsonSerializableAttribute") + { + return classDeclarationSyntax; + } + } + + } + + return null; + } + private static JsonSourceGenerationMode? GetJsonSourceGenerationModeEnumVal(SyntaxNode propertyValueMode) { IEnumerable enumTokens = propertyValueMode @@ -729,7 +762,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener if (!type.TryGetDeserializationConstructor(useDefaultCtorInAnnotatedStructs, out ConstructorInfo? constructor)) { classType = ClassType.TypeUnsupportedBySourceGen; - _executionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" })); + _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" })); } else { diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index 84212e8c19c78f..e155488975622b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +//#define LAUNCH_DEBUGGER using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -16,22 +18,21 @@ namespace System.Text.Json.SourceGeneration /// Generates source code to optimize serialization and deserialization with JsonSerializer. /// [Generator] - public sealed partial class JsonSourceGenerator : ISourceGenerator + public sealed partial class JsonSourceGenerator : IIncrementalGenerator { - /// - /// Registers a syntax resolver to receive compilation units. - /// - /// - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + IncrementalValuesProvider classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (s, _) => Parser.GetSemanticTargetForGeneration(s)) + .Where(static c => c is not null); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndClasses = + context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Item1, source.Item2, spc)); } - /// - /// Generates source code to optimize serialization and deserialization with JsonSerializer. - /// - /// - public void Execute(GeneratorExecutionContext executionContext) + private void Execute(Compilation compilation, ImmutableArray contextClasses, SourceProductionContext context) { #if LAUNCH_DEBUGGER if (!Diagnostics.Debugger.IsAttached) @@ -39,37 +40,22 @@ public void Execute(GeneratorExecutionContext executionContext) Diagnostics.Debugger.Launch(); } #endif - SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; - List? contextClasses = receiver.ClassDeclarationSyntaxList; - if (contextClasses == null) + if (contextClasses.IsDefaultOrEmpty) { return; } - Parser parser = new(executionContext); - SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList); + Parser parser = new(compilation, context); + SourceGenerationSpec? spec = parser.GetGenerationSpec(contextClasses); if (spec != null) { _rootTypes = spec.ContextGenerationSpecList[0].RootSerializableTypes; - Emitter emitter = new(executionContext, spec); + Emitter emitter = new(context, spec); emitter.Emit(); } } - private sealed class SyntaxReceiver : ISyntaxReceiver - { - public List? ClassDeclarationSyntaxList { get; private set; } - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax { AttributeLists.Count: > 0, BaseList.Types.Count: > 0 } cds) - { - (ClassDeclarationSyntaxList ??= new List()).Add(cds); - } - } - } - /// /// Helper for unit tests. /// diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs similarity index 90% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index 86a261f284e67a..7d1858d5e47a91 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -16,6 +17,11 @@ namespace System.Text.Json.SourceGeneration.UnitTests { public class CompilationHelper { + private static readonly CSharpParseOptions s_parseOptions = + new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse) + // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change. + .WithLanguageVersion(LanguageVersion.Preview); + public static Compilation CreateCompilation( string source, MetadataReference[] additionalReferences = null, @@ -55,18 +61,18 @@ public static Compilation CreateCompilation( return CSharpCompilation.Create( assemblyName, - syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) }, + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, s_parseOptions) }, references: references.ToArray(), options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) ); } - private static GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) + private static GeneratorDriver CreateDriver(Compilation compilation, IIncrementalGenerator[] generators) => CSharpGeneratorDriver.Create( - generators: ImmutableArray.Create(generators), - parseOptions: new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse)); + generators: generators.Select(g => g.AsSourceGenerator()), + parseOptions: s_parseOptions); - public static Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + public static Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params IIncrementalGenerator[] generators) { CreateDriver(compilation, generators).RunGeneratorsAndUpdateCompilation(compilation, out Compilation outCompilation, out diagnostics); return outCompilation; @@ -267,7 +273,15 @@ internal static void CheckDiagnosticMessages(ImmutableArray diagnost Array.Sort(actualMessages); Array.Sort(expectedMessages); - Assert.Equal(expectedMessages, actualMessages); + if (CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase)) + { + Assert.Equal(expectedMessages, actualMessages); + } + else + { + // for non-English runs, just compare the number of messages are the same + Assert.Equal(expectedMessages.Length, actualMessages.Length); + } } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.csproj similarity index 94% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.csproj index fad3b73e1026df..ef8eb335c61e1c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/System.Text.Json.SourceGeneration.UnitTests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Unit.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/TypeWrapperTests.cs similarity index 100% rename from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs rename to src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/TypeWrapperTests.cs diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index 14cf90630710d4..14ea741c8c5956 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -294,7 +294,7 @@ $(AppleTestTarget) - + false diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 38fd8bef71336a..79e47df5100e6d 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -233,6 +233,9 @@ + + + diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 1b57aa747896ef..3e032e76483b2d 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -277,6 +277,9 @@ Common\Interop\OSX\Interop.SearchPath.cs + + Common\Interop\OSX\Interop.SearchPath.iOS.cs + diff --git a/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs index 372805e70086e2..affed35d7b452c 100644 --- a/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -36,18 +36,14 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio { switch (folder) { - // TODO: fix for tvOS (https://github.com/dotnet/runtime/issues/34007) - // The "normal" NSDocumentDirectory is a read-only directory on tvOS - // and that breaks a lot of assumptions in the runtime and the BCL - case SpecialFolder.Personal: case SpecialFolder.LocalApplicationData: - return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory); + return CombineDocumentDirectory(string.Empty); case SpecialFolder.ApplicationData: // note: at first glance that looked like a good place to return NSLibraryDirectory // but it would break isolated storage for existing applications - return CombineSearchPath(NSSearchPathDirectory.NSDocumentDirectory, ".config"); + return CombineDocumentDirectory(".config"); case SpecialFolder.Resources: return Interop.Sys.SearchPath(NSSearchPathDirectory.NSLibraryDirectory); // older (8.2 and previous) would return String.Empty @@ -63,7 +59,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return Path.Combine(GetFolderPathCore(SpecialFolder.Personal, SpecialFolderOption.None), "Pictures"); case SpecialFolder.Templates: - return CombineSearchPath(NSSearchPathDirectory.NSDocumentDirectory, "Templates"); + return CombineDocumentDirectory("Templates"); case SpecialFolder.MyVideos: return Path.Combine(GetFolderPathCore(SpecialFolder.Personal, SpecialFolderOption.None), "Videos"); @@ -72,7 +68,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return "/usr/share/templates"; case SpecialFolder.Fonts: - return CombineSearchPath(NSSearchPathDirectory.NSDocumentDirectory, ".fonts"); + return CombineDocumentDirectory(".fonts"); case SpecialFolder.Favorites: return CombineSearchPath(NSSearchPathDirectory.NSLibraryDirectory, "Favorites"); @@ -100,6 +96,23 @@ static string CombineSearchPath(NSSearchPathDirectory searchPath, string subdire Path.Combine(path, subdirectory) : string.Empty; } + + static string CombineDocumentDirectory(string subdirectory) + { +#if TARGET_TVOS + string? path = CombineSearchPath(NSSearchPathDirectory.NSLibraryDirectory, Path.Combine("Caches", "Documents", subdirectory)); + // Special version of CombineSearchPath which creates the path if needed. + // This isn't needed for "real" search paths which always exist, but on tvOS + // the base path is really a subdirectory we define rather than an OS directory. + // In order to not treat Directory.Exists(SpecialFolder.ApplicationData) differently + // on tvOS, guarantee that it exists by creating it here + if (!Directory.Exists (path)) + Directory.CreateDirectory (path); +#else + string? path = CombineSearchPath(NSSearchPathDirectory.NSDocumentDirectory, subdirectory); +#endif + return path; + } } } } diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 369016e11892d6..34c1e7606d048e 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -1149,7 +1149,7 @@ arch_init (MonoAotCompile *acfg) if (strstr (acfg->aot_opts.mtriple, "ios")) { g_string_append (acfg->llc_args, " -mattr=+v7"); - g_string_append (acfg->llc_args, " -exception-model=dwarf -disable-fp-elim"); + g_string_append (acfg->llc_args, " -exception-model=dwarf -frame-pointer=all"); } } diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index 5d17be0c34a7dc..e1af42b9731ef6 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -5324,20 +5324,20 @@ handle_call_res_devirt (MonoCompile *cfg, MonoMethod *cmethod, MonoInst *call_re MonoClass *gcomparer = mono_class_get_geqcomparer_class (); g_assert (gcomparer); gcomparer_inst = mono_class_inflate_generic_class_checked (gcomparer, &ctx, error); - mono_error_assert_ok (error); + if (is_ok (error)) { + MONO_INST_NEW (cfg, typed_objref, OP_TYPED_OBJREF); + typed_objref->type = STACK_OBJ; + typed_objref->dreg = alloc_ireg_ref (cfg); + typed_objref->sreg1 = call_res->dreg; + typed_objref->klass = gcomparer_inst; + MONO_ADD_INS (cfg->cbb, typed_objref); - MONO_INST_NEW (cfg, typed_objref, OP_TYPED_OBJREF); - typed_objref->type = STACK_OBJ; - typed_objref->dreg = alloc_ireg_ref (cfg); - typed_objref->sreg1 = call_res->dreg; - typed_objref->klass = gcomparer_inst; - MONO_ADD_INS (cfg->cbb, typed_objref); + call_res = typed_objref; - call_res = typed_objref; - - /* Force decompose */ - cfg->flags |= MONO_CFG_NEEDS_DECOMPOSE; - cfg->cbb->needs_decompose = TRUE; + /* Force decompose */ + cfg->flags |= MONO_CFG_NEEDS_DECOMPOSE; + cfg->cbb->needs_decompose = TRUE; + } } }