Browse Source

Hide UI types in facade reference assemblies

Ian Griffiths 3 months ago
parent
commit
8df98cb6a9

+ 27 - 0
Rx.NET/Source/System.Reactive.sln

@@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Reactive.For.Windows
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive", "facades\System.Reactive\System.Reactive.csproj", "{BC5F1E6F-858E-4AF8-80CD-DBB5B6CE5FE1}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive.MakeRefAssemblies", "facades\System.Reactive.MakeRefAssemblies\System.Reactive.MakeRefAssemblies.csproj", "{20DF1979-FD74-DD5D-60B3-CE5874101E31}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug (with UWP)|Any CPU = Debug (with UWP)|Any CPU
@@ -624,6 +626,30 @@ Global
 		{BC5F1E6F-858E-4AF8-80CD-DBB5B6CE5FE1}.Release|x64.Build.0 = Release|Any CPU
 		{BC5F1E6F-858E-4AF8-80CD-DBB5B6CE5FE1}.Release|x86.ActiveCfg = Release|Any CPU
 		{BC5F1E6F-858E-4AF8-80CD-DBB5B6CE5FE1}.Release|x86.Build.0 = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|Any CPU.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|Any CPU.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|ARM.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|ARM.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|x64.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|x64.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|x86.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug (with UWP)|x86.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|ARM.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|x64.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Debug|x86.Build.0 = Debug|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|Any CPU.Build.0 = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|ARM.ActiveCfg = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|ARM.Build.0 = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x64.ActiveCfg = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x64.Build.0 = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x86.ActiveCfg = Release|Any CPU
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -648,6 +674,7 @@ Global
 		{C3FC6098-AC7F-4825-B292-4049BC6FC76E} = {1873A545-87AA-4C22-BA1A-8A6602F65749}
 		{EB27A089-56EC-4621-BF88-E3B0DA8E6557} = {1873A545-87AA-4C22-BA1A-8A6602F65749}
 		{BC5F1E6F-858E-4AF8-80CD-DBB5B6CE5FE1} = {A0F39260-B8F8-4FCB-9679-0ED917A22BDF}
+		{20DF1979-FD74-DD5D-60B3-CE5874101E31} = {A0F39260-B8F8-4FCB-9679-0ED917A22BDF}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {2483F58F-A8D6-4FFE-A3C1-10F3A36DBE69}

+ 102 - 0
Rx.NET/Source/facades/System.Reactive.MakeRefAssemblies/System.Reactive.MakeRefAssemblies.csproj

@@ -0,0 +1,102 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netstandard2.0;net472;uap10.0.18362;net8.0;net8.0-windows10.0.19041</TargetFrameworks>
+    <Nullable>enable</Nullable>
+
+    <!--
+    We seem to get spurious CA1812 warnings (internal class apparently never instantiated. The same code compiles
+    without warnings when built as a normal library, so this seems to be an issue with the refernce assembly build.
+    -->
+    <NoWarn>$(NoWarn);CA1812</NoWarn>
+
+    <DefineConstants>$(DefineConstants);LEGACY_SYSTEM_REACTIVE_FACADE;BUILDING_REFERENCE_ASSEMBLY</DefineConstants>
+
+    <ProduceOnlyReferenceAssembly>true</ProduceOnlyReferenceAssembly>
+
+    <TargetName>System.Reactive</TargetName>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!--
+    See comments in System.Reactive for explanation of this and the
+    PropertyGroup that follows.
+    -->
+    <Compile Remove="..\GlobalAssemblyVersion.cs" Link="GlobalAssemblyVersion.cs" />
+  </ItemGroup>
+
+  <PropertyGroup>
+    <!-- Re-instate the normal versioning behaviour. -->
+    <GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
+    <GenerateAssemblyVersionInfo>true</GenerateAssemblyVersionInfo>
+  </PropertyGroup>
+
+
+  <ItemGroup>
+    <Compile Include="..\System.Reactive\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);..\System.Reactive\obj\**\*;..\System.Reactive\bin\**\*" />
+    <Compile Remove="..\System.Reactive\Obsolete\**\*.*" />
+    <!-- Workaround so the files appear in VS -->
+    <None Include="..\System.Reactive\Obsolete\**\*.*" />
+  </ItemGroup>
+
+  <ItemGroup Condition="$(TargetFramework.StartsWith('uap10.0'))">
+    <Compile Include="..\System.Reactive\Obsolete\UWP\**\*.cs" />
+    <Compile Include="..\..\src\System.Reactive.For.Uwp\Stubs.cs" Link="Obsolete\UWP\Stubs.cs" />
+    <Compile Include="..\..\src\System.Reactive.For.Uwp\ThreadPoolTimerExtensions.cs" Link="Obsolete\UWP\Concurrency\ThreadPoolTimerExtensions.cs" />
+  </ItemGroup>
+
+  <!-- Windows includes for Desktop and UWP -->
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net472' or $(TargetFramework.StartsWith('uap10.0')) or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
+    <Compile Include="..\System.Reactive\Obsolete\Windows\**\*.cs" />
+    <EmbeddedResource Include="..\System.Reactive\Obsolete\Windows\**\*.resx" />
+  </ItemGroup>
+
+  <!-- .NET FX -->
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net472'">
+    <Reference Include="System.Windows" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="WindowsBase" />
+    
+    <Compile Include="..\System.Reactive\Obsolete\Remoting\**\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net472' or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
+    <Compile Include="..\System.Reactive\Obsolete\Desktop\**\*.cs" />
+  </ItemGroup>
+
+
+    <!-- WindowsRuntime (netX.0-windows and UWP) -->
+  <ItemGroup Condition="$(TargetFramework.StartsWith('uap10.0')) or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
+    <ProjectReference Include="..\..\src\System.Reactive.For.WindowsRuntime\System.Reactive.For.WindowsRuntime.csproj" />
+  </ItemGroup>
+
+  <!-- Windows Forms and WPF -->
+  <ItemGroup Condition="('$(TargetFramework)' == 'net472') or $(TargetFramework.StartsWith('net8.0-windows')) or $(TargetFramework.StartsWith('net9.0-windows'))">
+    <ProjectReference Include="..\..\src\System.Reactive.For.WindowsForms\System.Reactive.For.WindowsForms.csproj" />
+    <ProjectReference Include="..\..\src\System.Reactive.For.Wpf\System.Reactive.For.Wpf.csproj" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <!--
+    The .NET SDK doesn't expect TargetFrameworks to include uap10.0.18362, so it doesn't understand
+    that this project is capable of using projects that target .NET Standard 2.0, with the result
+    that it reports an error when trying to resolve the reference to System.Reactive.Net. So
+    when building for UWP, we explicitly set the target framework here.
+    -->
+    <ProjectReference Include="..\..\src\System.Reactive.Net\System.Reactive.Net.csproj">
+      <Aliases>SystemReactiveNet</Aliases>
+      <SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
+    </ProjectReference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFramework)'!='uap10.0.18362'">
+    <ProjectReference Include="..\..\src\System.Reactive.Net\System.Reactive.Net.csproj">
+      <Aliases>SystemReactiveNet</Aliases>
+    </ProjectReference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFramework)'=='uap10.0.18362'">
+    <ReferencePath Include="$(TargetPlatformSdkPath)UnionMetadata\10.0.19041.0\Windows.winmd" />
+  </ItemGroup>
+
+</Project>

+ 183 - 12
Rx.NET/Source/facades/System.Reactive/ApiCompatSuppressions.xml

@@ -2,22 +2,109 @@
 <!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
 <Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 
+  <!--
+  We deliberately exclude certain types from the reference assemblies.
+
+  Specifically, in the netX.0-windows10.0.X target, we need the runtime libraries to include the same API
+  surface area as System.Reactive v6.0.1, but we have to remove types that define extension methods for
+  IObservable<T> where those extension methods use UI-framework-specific types. For example, we don't want
+  the ObserveOn(Control) to be available. That's because if it _is_ visible to the compiler, it becomes impossible
+  to use the non-UI-specific overloads of ObserveOn without having a reference to the Microsoft.WindowsDesktop.App
+  framework! (So even when you aren't using the Windows Forms or WPF overloads, the compiler needs access to
+  the Windows Forms and WPF assemblies to be able to determine that you aren't using ObserveOn(Control) or
+  ObserveOn(Dispatcher).
+
+  That would make it impossible to use the System.Reactive legacy package, and also to use Rx in your app, without
+  also have a desktop framework dependency. And since the whole reason we're relegating System.Reactive to being
+  a legacy facade is to fix that very problem, this would be pointless.
+
+  So we hide all the UI-specific types from the reference assemblies for the .NET Windows-specific targets.
+  (We don't have to do that for .NET FX or UAP targets because if you're building for those, you already have
+  the relevant UI frameworks installed.)
+  -->
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.ControlScheduler</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.CoreDispatcherScheduler</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.DispatcherScheduler</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.AsyncInfoObservable</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.ControlObservable</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.CoreDispatcherObservable</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.DispatcherObservable</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.WindowsObservable</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Windows.Foundation.AsyncInfoObservableExtensions</Target>
+    <Left>ref/net8.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>lib/net8.0-windows10.0.19041/System.Reactive.dll</Right>
+  </Suppression>
+
   <!--
   There are various internal (same package) inconsistencies that were already in Rx 6.0, because the API surface
   area was different across targets. There doesn't seem to be a 'baseline-only' mode, so we need to suppress
   the various validation errors that describe differences that we must preserve for compatibility with Rx 6.0.
 
   Essentially these all arise because netstandard2.0 is a viable target for frameworks in which other targets
-  are better matches. (E.g., if you target net6.0-windows10-0.19041, then you can actually tell the build system
-  to give you the .NET Standard 2.0 target of System.Reactive.dll, because you're on a runtime that supports
-  .NET Standard 2.0. By default you'd get net6.0-windows10-0.19041 because that's a better match (a precise match
-  in fact), but since you can override that and get the netstandard2.0 target instead, package validation sees
-  fit to tell us all the ways in which the netstandard2.0 target differs from the net6.0-windows10-0.19041 target.
-  Similarly, uap10.0.1836 is a framework that supports netstandard2.0, so again, the package validation tells
-  us about the differences between the netstandard2.0 and uap10.0.18362 targets.
+  are better matches. An application that targets net8.0-windows10-0.19041, is able to use any netstandard2.0
+  library, and any of those might have been built against Rx. Since the net8.0-windows10.0.19041 target is a
+  better match for the app, that's the version of Rx that will be chosen, but it needs to be consistent with
+  the netstandard2.0 one, because other libraries might have been built against that one.
+
+  The analyzer seems a little aggressive in that not only does it require that targets that might be substituted
+  for the netstandard2.0 target must provide the same surface area as the netstandard2.0 target, it also requires
+  that they don't provide any API surface area that is not present in the netstandard2.0 target. That seems more
+  odd because it's not obvious why something might have been built against the net8.0-windows10.0.19041 target
+  only to find itself using the netstandard2.0 target at runtime.
+
+  We mostly only have inconsistencies of the second kind. (I.e., there's almost nothing in the netstandard2.0 target
+  that is missing from more specific targets. The one exception is that the ThreadPoolScheduler implements
+  ISchedulerLongRunning on all targets except uap10.0.18362.) But we have plenty of examples where the more
+  specific targets provide additional API surface area that is not present in the netstandard2.0 target. It seems
+  like there really should be a way to turn off detection of that second kind, because it's normal. (The main reason
+  we offer targets in addition to netstandard2.0 is to add additional features.) But if there's a setting to do this,
+  I've not found it.
 
   We only care about differences between the baseline (6.0.1) and the current package, so we don't really want
-  to hear about internal inconstencies, but apparently, suppressing them is the only option.
+  to hear about internal inconstencies, but apparently, suppressing them is the only option. None of the options
+  for turning off strict mode seem to help.
 
   The CP0001 errors all describes types that are present in one DLL but not another.
   -->
@@ -29,6 +116,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.ControlScheduler</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Concurrency.CoreDispatcherScheduler</Target>
@@ -36,6 +130,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.CoreDispatcherScheduler</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Concurrency.DispatcherScheduler</Target>
@@ -43,6 +144,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Concurrency.DispatcherScheduler</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.IEventPatternSource`2</Target>
@@ -50,6 +158,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.IEventPatternSource`2</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Linq.AsyncInfoObservable</Target>
@@ -57,6 +172,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.AsyncInfoObservable</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Linq.ControlObservable</Target>
@@ -64,6 +186,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.ControlObservable</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Linq.CoreDispatcherObservable</Target>
@@ -71,6 +200,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.CoreDispatcherObservable</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Linq.DispatcherObservable</Target>
@@ -78,6 +214,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.DispatcherObservable</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Linq.WindowsObservable</Target>
@@ -85,6 +228,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Linq.WindowsObservable</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0001</DiagnosticId>
     <Target>T:System.Reactive.Windows.Foundation.AsyncInfoObservableExtensions</Target>
@@ -92,6 +242,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0001</DiagnosticId>
+    <Target>T:System.Reactive.Windows.Foundation.AsyncInfoObservableExtensions</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
 
 
   <!--
@@ -110,8 +267,8 @@
   <Suppression>
     <DiagnosticId>CP0008</DiagnosticId>
     <Target>T:System.Reactive.Concurrency.ThreadPoolScheduler</Target>
-    <Left>lib/netstandard2.0/System.Reactive.dll</Left>
-    <Right>lib/uap10.0.18362/System.Reactive.dll</Right>
+    <Left>ref/netstandard2.0/System.Reactive.dll</Left>
+    <Right>ref/uap10.0.18362/System.Reactive.dll</Right>
   </Suppression>
 
   <!--
@@ -125,8 +282,8 @@
   <Suppression>
     <DiagnosticId>CP0002</DiagnosticId>
     <Target>M:System.Reactive.Concurrency.ThreadPoolScheduler.ScheduleLongRunning``1(``0,System.Action{``0,System.Reactive.Disposables.ICancelable})</Target>
-    <Left>lib/netstandard2.0/System.Reactive.dll</Left>
-    <Right>lib/uap10.0.18362/System.Reactive.dll</Right>
+    <Left>ref/netstandard2.0/System.Reactive.dll</Left>
+    <Right>ref/uap10.0.18362/System.Reactive.dll</Right>
   </Suppression>
 
 
@@ -148,6 +305,13 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0008</DiagnosticId>
+    <Target>T:System.Reactive.NotificationKind</Target>
+    <Left>lib/net6.0-windows10.0.19041/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0008</DiagnosticId>
     <Target>T:System.Reactive.NotificationKind</Target>
@@ -155,4 +319,11 @@
     <Right>lib/netstandard2.0/System.Reactive.dll</Right>
     <IsBaselineSuppression>true</IsBaselineSuppression>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0008</DiagnosticId>
+    <Target>T:System.Reactive.NotificationKind</Target>
+    <Left>lib/net6.0/System.Reactive.dll</Left>
+    <Right>ref/netstandard2.0/System.Reactive.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
 </Suppressions>

+ 15 - 0
Rx.NET/Source/facades/System.Reactive/Obsolete/Remoting/Linq/Observable.Remoting.cs

@@ -2,6 +2,21 @@
 // The .NET Foundation licenses this file to you under the MIT License.
 // See the LICENSE file in the project root for more information. 
 
+// Remoting types now live only in the System.Reactive legacy facade package (meaning that this isn't
+// strictly a pure facade package).
+// Nobody should be using Remoting any more. And the new System.Reactive.Net package does not include
+// a net472 target. If this type remained in there, that would be the only reason it would need to offer
+// that target.
+// We could move this into a separate package entirely, but unless we discover that people really want it,
+// the addition of a whole new package just to provide .NET Remoting support would seem like we were
+// encouraging people to use a feature that they should not. it can remain in here. If nobody complains,
+// we can mark it as obsolete in a future release.
+// (We can't put it back into the old System.Reactive.Runtime.Remoting package and forward to there from
+// here, because existing versions of that package contain type forwarders back to this to System.Reactive,
+// package, and if people managed to get a slightly weird combination of versions of different Rx packages
+// - and that's certainly a thing that has happened in the past to disastrous effect - they would end up
+// with circular type forwarders.)
+
 #if HAS_REMOTING
 extern alias SystemReactiveNet;
 

+ 2 - 5
Rx.NET/Source/facades/System.Reactive/Obsolete/Remoting/Linq/QueryLanguage.Remoting.cs

@@ -2,11 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT License.
 // See the LICENSE file in the project root for more information. 
 
-// TODO: does this belong in here or System.Reactive.Net?
-// We might be able to completely remove the net472 target from System.Reactive.Net, in which case this would have
-// to come out, but that does raise the question of where the thing should live. Back in System.Reactive.Runtime.Remoting?
-// Or does that risk creating circular type forwarders if versions get out of alignment?
-// We don't really want it to remain in System.Reactive since we're trying to deprecate this component.
+// See comment in Observable.Remoting.cs for why we build this code into System.Reactive, which is mostly
+// just a type forwarding legacy facade.
 
 #if HAS_REMOTING
 extern alias SystemReactiveNet;

+ 6 - 0
Rx.NET/Source/facades/System.Reactive/Obsolete/UWP/Concurrency/ThreadPoolScheduler.Windows.cs

@@ -30,6 +30,12 @@ namespace System.Reactive.Concurrency
         [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. Otherwise, use the Instance property, because this constructor will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")]
         public ThreadPoolScheduler()
         {
+            // The next step for obsolescence is to omit this constructor and all the other
+            // Obsolete methods when BUILDING_REFERENCE_ASSEMBLY is defined.
+            // That way, they will remain available at runtime, providing binary backwards compatibility,
+            // but it will force anyone building against the latest Rx to use the replacement
+            // UwpThreadPoolScheduler type.
+            // But we're not doing that yet, because we want an obsolete-but-available period.
         }
 
         /// <summary>

+ 31 - 0
Rx.NET/Source/facades/System.Reactive/System.Reactive.csproj

@@ -155,4 +155,35 @@
     <ReferencePath Include="$(TargetPlatformSdkPath)UnionMetadata\10.0.19041.0\Windows.winmd" />
   </ItemGroup>
 
+  <!--
+  Put the reference assemblies built by System.Reactive.MakeRefAssemblies into the 'ref' folder of our
+  NuGet package.
+  -->
+  <ItemGroup>
+    <ProjectReference Include="..\System.Reactive.MakeRefAssemblies\System.Reactive.MakeRefAssemblies.csproj" PrivateAssets="all">
+      <!--
+      If we don't set an alias here, all the types exposed by the reference assembly become available in the global namespace,
+      which causes compiler errors when the UAP target tries to build a specialized ThreadPoolScheduler.
+      -->
+      <Aliases>SystemReactiveRefAssembly</Aliases>
+    </ProjectReference>
+  </ItemGroup>
+
+  <!--
+  We need to supply our own nuspec file, because the SDK doesn't appear to be able to populate the <references> section,
+  and without that we get NU5131 warnings. (I think this is only strictly necessary for package.config scenarios, but
+  Rx has a lot of users, so we'll probably break someone if we don't get this right.)
+  We use tokenization so that we can pass package metadata (which include the version number). But it turns out that
+  the various NuGet package metadata build properties we want to pass in don't get defined until quite late in the build,
+  so we need to set <NuspecProperties> in a custom target that runs just before the GenerateNuspec target.
+  -->
+  <PropertyGroup>
+    <NuspecFile>System.Reactive.nuspec</NuspecFile>
+  </PropertyGroup>
+  <Target Name="_SetNuspecProperties" BeforeTargets="GenerateNuspec">
+    <PropertyGroup>
+      <NuspecProperties>id=$(PackageId);version=$(PackageVersion);authors=$(Authors);licenseExpression=$(PackageLicenseExpression);icon=$(PackageIcon);projectUrl=$(PackageProjectUrl);description=$(PackageDescription);copyright=$(Copyright);tags=$(PackageTags);repository=$(RepositoryUrl);commit=$(RepositoryCommit);outputpath=$(OutputPath);refassemblypath=..\System.Reactive.MakeRefAssemblies\bin\$(Configuration)\</NuspecProperties>
+    </PropertyGroup>
+  </Target>
+
 </Project>

+ 79 - 0
Rx.NET/Source/facades/System.Reactive/System.Reactive.nuspec

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
+  <metadata>
+    <id>$id$</id>
+    <version>$version$</version>
+    <authors>$authors$</authors>
+    <license type="expression">$licenseExpression$</license>
+    <icon>$icon$</icon>
+    <readme>readme.md</readme>
+    <projectUrl>$projectUrl$</projectUrl>
+    <description>$description$</description>
+    <copyright>$copyright$</copyright>
+    <tags>$tags$</tags>
+    <repository type="git" url="$repository$" commit="$commit$" />
+    <references>
+      <group targetFramework="net8.0">
+        <reference file="System.Reactive.dll" />
+      </group>
+      <group targetFramework="net8.0-windows10.0.19041">
+        <reference file="System.Reactive.dll" />
+      </group>
+      <group targetFramework="netstandard2.0">
+        <reference file="System.Reactive.dll" />
+      </group>
+      <group targetFramework="net472">
+        <reference file="System.Reactive.dll" />
+      </group>
+      <group targetFramework="uap10.0.18362">
+        <reference file="System.Reactive.dll" />
+      </group>
+    </references>
+    <dependencies>
+      <group targetFramework=".NETFramework4.7.2">
+        <dependency id="System.Threading.Tasks.Extensions" version="4.5.4" exclude="Build,Analyzers" />
+      </group>
+      <group targetFramework="net8.0" />
+      <group targetFramework="net8.0-windows10.0.19041" />
+      <group targetFramework=".NETStandard2.0">
+        <dependency id="System.Threading.Tasks.Extensions" version="4.5.4" exclude="Build,Analyzers" />
+      </group>
+      <group targetFramework="UAP10.0.18362">
+        <dependency id="System.Threading.Tasks.Extensions" version="4.5.4" exclude="Build,Analyzers" />
+      </group>
+    </dependencies>
+    <frameworkReferences>
+      <group targetFramework="net8.0" />
+      <group targetFramework="net8.0-windows10.0.19041">
+        <frameworkReference name="Microsoft.WindowsDesktop.App" />
+      </group>
+      <group targetFramework=".NETFramework4.7.2" />
+      <group targetFramework=".NETStandard2.0" />
+      <group targetFramework="UAP10.0.18362" />
+    </frameworkReferences>
+    <frameworkAssemblies>
+      <frameworkAssembly assemblyName="System.Windows" targetFramework=".NETFramework4.7.2" />
+      <frameworkAssembly assemblyName="System.Windows.Forms" targetFramework=".NETFramework4.7.2" />
+      <frameworkAssembly assemblyName="WindowsBase" targetFramework=".NETFramework4.7.2" />
+    </frameworkAssemblies>
+  </metadata>
+  <files>
+    <file src="$outputpath$net472\System.Reactive.dll" target="lib\net472\System.Reactive.dll" />
+    <file src="$outputpath$net8.0\System.Reactive.dll" target="lib\net8.0\System.Reactive.dll" />
+    <file src="$outputpath$net8.0-windows10.0.19041\System.Reactive.dll" target="lib\net8.0-windows10.0.19041\System.Reactive.dll" />
+    <file src="$outputpath$netstandard2.0\System.Reactive.dll" target="lib\netstandard2.0\System.Reactive.dll" />
+    <file src="$outputpath$uap10.0.18362\System.Reactive.dll" target="lib\uap10.0.18362\System.Reactive.dll" />
+    <file src="$refassemblypath$net472\System.Reactive.dll" target="ref\net472\System.Reactive.dll" />
+    <file src="$refassemblypath$net8.0\System.Reactive.dll" target="ref\net8.0\System.Reactive.dll" />
+    <file src="$refassemblypath$net8.0-windows10.0.19041\System.Reactive.dll" target="ref\net8.0-windows10.0.19041\System.Reactive.dll" />
+    <file src="$refassemblypath$netstandard2.0\System.Reactive.dll" target="ref\netstandard2.0\System.Reactive.dll" />
+    <file src="$refassemblypath$uap10.0.18362\System.Reactive.dll" target="ref\uap10.0.18362\System.Reactive.dll" />
+    <file src="..\..\..\Resources\Artwork\Logo.png" target="icon.png" />
+    <file src="..\NuGet.Facades.Readme.md" target="readme.md" />
+    <file src="$outputpath$net472\System.Reactive.pdb" target="lib\net472\System.Reactive.pdb" />
+    <file src="$outputpath$net8.0\System.Reactive.pdb" target="lib\net8.0\System.Reactive.pdb" />
+    <file src="$outputpath$net8.0-windows10.0.19041\System.Reactive.pdb" target="lib\net8.0-windows10.0.19041\System.Reactive.pdb" />
+    <file src="$outputpath$netstandard2.0\System.Reactive.pdb" target="lib\netstandard2.0\System.Reactive.pdb" />
+    <file src="$outputpath$uap10.0.18362\System.Reactive.pdb" target="lib\uap10.0.18362\System.Reactive.pdb" />
+  </files>
+</package>

+ 4 - 6
Rx.NET/Source/facades/System.Reactive/System/Reactive/Concurrency.cs

@@ -54,12 +54,9 @@ using System.Runtime.CompilerServices;
 [assembly:TypeForwardedToAttribute(typeof(VirtualTimeScheduler<,>))]
 [assembly:TypeForwardedToAttribute(typeof(VirtualTimeSchedulerExtensions))]
 
-// TODO: currently we've moved these three types into this assembly, with the intention of deprecating them all,
-// and preferring the newer types in the platform-specific packages.
-// An alternative would be to have the types retain their original names and live in those new packages without
-// deprecating them. This would enable us to avoid making working types obsolete which might reduce confusion.
-// It does remove the opportunity to fix up some naming issues, but maybe it would be better to live with that
-// to minimize disruption.
+// Seem comments in Reactive\Linq.cs for why we exclude forwarders for these types in some reference assemblies.
+#if !(BUILDING_REFERENCE_ASSEMBLY && NET8_0_OR_GREATER)
+
 #if WINDOWS
 [assembly:TypeForwardedToAttribute(typeof(System.Reactive.Concurrency.CoreDispatcherScheduler))]
 #endif
@@ -69,3 +66,4 @@ using System.Runtime.CompilerServices;
 #if HAS_DISPATCHER
 [assembly:TypeForwardedToAttribute(typeof(System.Reactive.Concurrency.DispatcherScheduler))]
 #endif
+#endif

+ 15 - 3
Rx.NET/Source/facades/System.Reactive/System/Reactive/Linq.cs

@@ -19,6 +19,20 @@ using System.Runtime.CompilerServices;
 [assembly:TypeForwardedToAttribute(typeof(QbservableEx))]
 [assembly:TypeForwardedToAttribute(typeof(QueryDebugger))]
 
+// These UI-framework-specific types have been moved to the platform-specific packages. To maintain binary
+// compatibility we forward the types to the new packages. However, when building reference assemblies for
+// the netX.0-windows10.0.X target, we do not include these types in the reference assembly. We do this to
+// enable applications that target a Windows-specific .NET TFM to use Rx without needing to reference the
+// Microsoft.WindowsDesktop.App framework. If we don't hide the UI-framework-specific types, they become
+// visible to the compiler, which can then complain that it can't find the Windows Forms or WPF types
+// that these types use. (In particular, this becomes a problem for the types that add UI-framework-specific
+// extension methods such as ObserveOn(Control). Code that is trying to use the non-UI-framework-specific
+// overloads fails to compile because the compiler can't find the Control type, and so it doesn't have
+// the information it requires to be determine that the Control overloads is not applicable.) So we
+// exclude UI-framework-specific types from the .NET '-windows' reference assembly, but we keep the
+// forwarders in the runtime assembly.
+
+#if !(BUILDING_REFERENCE_ASSEMBLY && NET8_0_OR_GREATER)
 #if WINDOWS
 [assembly:TypeForwardedToAttribute(typeof(System.Reactive.Linq.AsyncInfoObservable))]
 [assembly:TypeForwardedToAttribute(typeof(System.Reactive.Linq.CoreDispatcherObservable))]
@@ -30,6 +44,4 @@ using System.Runtime.CompilerServices;
 #if HAS_DISPATCHER
 [assembly: TypeForwardedToAttribute(typeof(System.Reactive.Linq.DispatcherObservable))]
 #endif
-//#if HAS_REMOTING
-//[assembly: TypeForwardedTo(typeof(RemotingObservable))]
-//#endif
+#endif

+ 5 - 1
Rx.NET/Source/facades/System.Reactive/System/Reactive/Windows/Foundation.cs

@@ -2,11 +2,15 @@
 // The .NET Foundation licenses this file to you under the MIT License.
 // See the LICENSE file in the project root for more information.
 
-#if WINDOWS
+// Seem comments in Reactive\Linq.cs for why we exclude forwarders for these types in some reference assemblies.
+#if !(BUILDING_REFERENCE_ASSEMBLY && NET8_0_OR_GREATER)
 
+#if WINDOWS
 
 using System.Runtime.CompilerServices;
 
 [assembly:TypeForwardedToAttribute(typeof(System.Reactive.Windows.Foundation.AsyncInfoObservableExtensions))]
 
 #endif
+
+#endif

+ 5 - 2
Rx.NET/Source/src/System.Reactive.For.Uwp/System.Reactive.For.Uwp.csproj

@@ -48,8 +48,11 @@
   <ItemGroup>
     <Compile Include="..\AssemblyInfo.cs" Link="Properties\AssemblyInfo.cs" />
     <Compile Include="..\System.Reactive.For.WindowsRuntime\AsyncInfoExtensions.cs" Link="AsyncInfoExtensions.cs" />
-    <Compile Include="..\System.Reactive.Net\Concurrency\UserWorkItem.cs" Link="UserWorkItem.cs" />
-    <Compile Include="..\System.Reactive.Net\Diagnostics\CodeAnalysis\NullableAttributes.cs" Link="NullableAttributes.cs" />
+
+    <!--
+    Although System.Reactive.Net makes its internals available to us, we get the netstandard2.0 target when running
+    on UWP, which doesn't include this particular file, so we need to build our own copy.
+    -->
     <Compile Include="..\System.Reactive.Net\Internal\HostLifecycleNotifications.Windows.cs" Link="HostLifecycleNotifications.Windows.cs" />
   </ItemGroup>
 

+ 1 - 1
Rx.NET/Source/version.json

@@ -1,5 +1,5 @@
 {
-  "version": "7.0.0-preview-legacyfacade.{height}",
+  "version": "7.0.0-preview-legacyfacade-refnoui.{height}",
   "publicReleaseRefSpec": [
     "^refs/heads/main$", // we release out of main
     "^refs/heads/rel/v\\d+\\.\\d+", // we also release branches starting with rel/vN.N