diff --git a/.github/templates/test-weaver.yml b/.github/templates/test-weaver.yml index 58d23fe138..8552a8806f 100644 --- a/.github/templates/test-weaver.yml +++ b/.github/templates/test-weaver.yml @@ -1,5 +1,5 @@ #@ load("@ytt:template", "template") -#@ load("common.lib.yml", "checkoutCode", "dotnetPublish") +#@ load("common.lib.yml", "checkoutCode", "dotnetPublish", "setupWorkloads") #@ load("test.lib.yml", "publishTestsResults") --- @@ -11,12 +11,22 @@ env: DOTNET_NOLOGO: true jobs: run-tests-weaver: - runs-on: windows-latest + strategy: + matrix: + os: + - runner: windows-latest + runtime: win-x64 + - runner: ubuntu-latest + runtime: linux-x64 + - runner: macos-latest + runtime: osx-x64 + runs-on: ${{ matrix.os.runner }} name: Weaver timeout-minutes: 15 steps: - #@ template.replace(checkoutCode()) - - #@ template.replace(dotnetPublish("Tests/Weaver/Realm.Fody.Tests", "netcoreapp3.1", "win-x64")) + - #@ setupWorkloads("android ${{ (matrix.os.runner != 'ubuntu-latest' && 'tvos ios maccatalyst') || '' }}") + - #@ template.replace(dotnetPublish("Tests/Weaver/Realm.Fody.Tests", "net6.0", "${{ matrix.os.runtime }}")) - name: Run Tests run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Fody.Tests --result=TestResults.Weaver.xml --labels=After - - #@ publishTestsResults("TestResults.Weaver.xml", "Weaver") + - #@ publishTestsResults("TestResults.Weaver.xml", "Weaver ${{ matrix.os.runtime }}") diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 5c53445867..c3a86d278b 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -14,6 +14,21 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 + with: + submodules: recursive + - name: Read Core version + id: get-core-version + run: | + cd wrappers/realm-core + pkgVersion=$(grep "\bVERSION=" dependencies.list | cut -d= -f2) + echo "core-version=$pkgVersion" >> $GITHUB_OUTPUT + shell: bash + - name: Update Analytics.cs + uses: jacobtomlinson/gha-find-replace@0dfd0777cc234ef6947ec1f20873c632114c4167 #! 0.1.4 + with: + find: 'CoreVersion = "\w*"' + replace: 'CoreVersion = "${{ steps.get-core-version.outputs.core-version }}"' + include: Realm/Realm.Weaver/Analytics/Analytics.cs - name: Update Changelog id: update-changelog uses: realm/ci-actions/update-changelog@729a80d203351eab7df4eca564daa275e76ec52f diff --git a/.github/workflows/test-weaver.yml b/.github/workflows/test-weaver.yml index 3d24a1c89d..eaba7939fc 100755 --- a/.github/workflows/test-weaver.yml +++ b/.github/workflows/test-weaver.yml @@ -6,7 +6,16 @@ env: DOTNET_NOLOGO: true jobs: run-tests-weaver: - runs-on: windows-latest + strategy: + matrix: + os: + - runner: windows-latest + runtime: win-x64 + - runner: ubuntu-latest + runtime: linux-x64 + - runner: macos-latest + runtime: osx-x64 + runs-on: ${{ matrix.os.runner }} name: Weaver timeout-minutes: 15 steps: @@ -19,11 +28,13 @@ jobs: run: echo "::add-matcher::.github/problem-matchers/csc.json" - name: Register msvc problem matcher run: echo "::add-matcher::.github/problem-matchers/msvc.json" + - name: Setup workloads + run: dotnet workload install android ${{ (matrix.os.runner != 'ubuntu-latest' && 'tvos ios maccatalyst') || '' }} - name: Publish Tests/Weaver/Realm.Fody.Tests - run: dotnet publish Tests/Weaver/Realm.Fody.Tests -c Release -f netcoreapp3.1 -r win-x64 --no-self-contained + run: dotnet publish Tests/Weaver/Realm.Fody.Tests -c Release -f net6.0 -r ${{ matrix.os.runtime }} --no-self-contained - name: Output executable path id: dotnet-publish - run: echo 'executable-path=./Tests/Weaver/Realm.Fody.Tests/bin/Release/netcoreapp3.1/win-x64' >> $GITHUB_OUTPUT + run: echo 'executable-path=./Tests/Weaver/Realm.Fody.Tests/bin/Release/net6.0/${{ matrix.os.runtime }}' >> $GITHUB_OUTPUT shell: bash - name: Run Tests run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Fody.Tests --result=TestResults.Weaver.xml --labels=After @@ -31,7 +42,7 @@ jobs: uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() with: - name: Results Weaver + name: Results Weaver ${{ matrix.os.runtime }} path: TestResults.Weaver.xml reporter: java-junit list-suites: failed diff --git a/CHANGELOG.md b/CHANGELOG.md index e1a499bfc1..2eb7f71987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,8 @@ var matches = realm.All().Where(c => QueryMethods.GeoWithin(c.Location, circle)); var matches = realm.All().Filter("Location GEOWITHIN $0", circle); ``` +* Support sort/distinct based on values from a dictionary e.g. `realm.All().Filter("TRUEPREDICATE SORT(meta['age'])")`. (Core 13.14.0) +* Fixed a potential crash when opening the realm after failing to download a fresh FLX realm during an automatic client reset. (Core 13.14.0) ### Fixed * Fixed a fatal error (reported to the sync error handler) during client reset (or automatic PBS to FLX migration) if the reset has been triggered during an async open and the schema being applied has added new classes. (Core 13.11.0) @@ -86,7 +88,8 @@ * Realm Studio: 13.0.0 or later. ### Internal -* Using Core 13.13.0. +* Using Core 13.15.0. +* Overhauled and extended the metrics collection of the SDK to better drive future development effort. (PR [#3209](https://github.com/realm/realm-dotnet/pull/3209)) ## 11.0.0 (2023-05-08) diff --git a/Realm - Windows.sln b/Realm - Windows.sln index 24110f6aac..df7daff957 100644 --- a/Realm - Windows.sln +++ b/Realm - Windows.sln @@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorAssemblyToPr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Maui", "Tests\Tests.Maui\Tests.Maui.csproj", "{C84EBA8B-5F7F-4519-BB34-EDE93E275D66}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeployApps", "Tools\DeployApps\DeployApps.csproj", "{10026D09-FC37-48B3-AAEA-B322AA6D89CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{536C3309-F848-4485-ABF3-56DCD9C9F9E8}" @@ -298,8 +300,9 @@ Global {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|iPhone.Build.0 = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x64.ActiveCfg = Debug|Any CPU - {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x64.Build.0 = Debug|Any CPU + {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x64.ActiveCfg = Debug|x64 + {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x64.Build.0 = Debug|x64 + {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x64.Deploy.0 = Debug|x64 {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x86.ActiveCfg = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.Debug|x86.Build.0 = Debug|Any CPU {10725A22-B22F-46AB-B5C4-7BC505A1E74A}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU @@ -1330,6 +1333,54 @@ Global {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {C84EBA8B-5F7F-4519-BB34-EDE93E275D66}.RelWithDebInfo|x86.Deploy.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|ARM.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhone.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x64.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x64.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x86.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Debug|x86.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|ARM.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|ARM.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhone.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhone.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|iPhoneSimulator.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.MinSizeRel|x86.Build.0 = Debug|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|Any CPU.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|ARM.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|ARM.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhone.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhone.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x64.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x64.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x86.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.Release|x86.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhone.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhone.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|iPhoneSimulator.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {10026D09-FC37-48B3-AAEA-B322AA6D89CE}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -1506,6 +1557,7 @@ Global {BCC2A759-231C-405C-BE9C-0C473365B232} = {EC97E75C-3A79-4B00-95BD-218D71C58746} {3C3CEB09-94C5-4FE4-BF75-1CEF4EAF6E47} = {EC97E75C-3A79-4B00-95BD-218D71C58746} {C84EBA8B-5F7F-4519-BB34-EDE93E275D66} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} + {1FBD9F7D-2C7F-4788-9C1E-22A40549AFA5} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} {10026D09-FC37-48B3-AAEA-B322AA6D89CE} = {073F6C2D-FECB-41E3-BEA3-1685FAA580DB} {536C3309-F848-4485-ABF3-56DCD9C9F9E8} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} {80B9697D-0C57-40E8-A71A-F5E81C7BF467} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} diff --git a/Realm.sln b/Realm.sln index ae84417497..065cfc45ea 100644 --- a/Realm.sln +++ b/Realm.sln @@ -85,6 +85,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SetupUnityPackage", "Tools\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Realm.PlatformHelpers", "Realm\Realm.PlatformHelpers\Realm.PlatformHelpers.csproj", "{D08C71CE-0F5B-4855-A77C-58062A5ECB78}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnalyticsAssembly", "Tests\Weaver\AnalyticsAssembly\AnalyticsAssembly.csproj", "{1E392D99-D783-4122-8B31-A4CA19ABADEE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -725,6 +727,30 @@ Global {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x64.Build.0 = Release|Any CPU {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x86.ActiveCfg = Release|Any CPU {D08C71CE-0F5B-4855-A77C-58062A5ECB78}.Release|x86.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|ARM.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhone.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x64.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Debug|x86.Build.0 = Debug|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|Any CPU.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|ARM.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|ARM.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhone.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhone.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x64.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x64.Build.0 = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x86.ActiveCfg = Release|Any CPU + {1E392D99-D783-4122-8B31-A4CA19ABADEE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -760,6 +786,7 @@ Global {7EFF9E5C-5E74-469B-8DB7-C25C9AF0444E} = {D10BE048-9C20-4B8B-BE5B-48CC55F8BB07} {A9B5E8CA-E1B8-47E4-89D4-8A55327F4121} = {A25317DE-BB3A-47CC-8E65-F96C9B6AD984} {D08C71CE-0F5B-4855-A77C-58062A5ECB78} = {50F058DF-2B41-403C-BB73-8B4180D1CF39} + {1E392D99-D783-4122-8B31-A4CA19ABADEE} = {4FF9AAE6-210E-41F0-A5E1-8D7C4A864F51} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BE5E0028-B74D-4BE1-B1DA-5FFCC8469C41} diff --git a/Realm.sln.DotSettings b/Realm.sln.DotSettings new file mode 100644 index 0000000000..237ab21fe4 --- /dev/null +++ b/Realm.sln.DotSettings @@ -0,0 +1,20 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/Realm/Realm.Fody/AssemblyInfo.cs b/Realm/Realm.Fody/AssemblyInfo.cs new file mode 100644 index 0000000000..fe4745bde4 --- /dev/null +++ b/Realm/Realm.Fody/AssemblyInfo.cs @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2023 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Realm.Fody.Tests")] diff --git a/Realm/Realm.Fody/GlobalSuppressions.cs b/Realm/Realm.Fody/GlobalSuppressions.cs index cca42cfec3..dc758c8252 100644 --- a/Realm/Realm.Fody/GlobalSuppressions.cs +++ b/Realm/Realm.Fody/GlobalSuppressions.cs @@ -13,3 +13,4 @@ [assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Nicer organization that way.", Scope = "type", Target = "~T:RealmWeaver.WeaveTypeResult")] [assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Nicer organization that way", Scope = "type", Target = "~T:RealmWeaver.WeavePropertyResult")] [assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "It doesn't really conflict with anything.", Scope = "member", Target = "~M:RealmWeaver.ILogger.Error(System.String,Mono.Cecil.Cil.SequencePoint)")] +[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "In C# 9.0 we can use new() to instantiate objects and we don't need a space there", Scope = "module")] diff --git a/Realm/Realm.Fody/ModuleWeaver.cs b/Realm/Realm.Fody/ModuleWeaver.cs index fb9d76da89..f2cc8995d6 100644 --- a/Realm/Realm.Fody/ModuleWeaver.cs +++ b/Realm/Realm.Fody/ModuleWeaver.cs @@ -22,8 +22,10 @@ using System.Runtime.Versioning; using Mono.Cecil.Cil; using RealmWeaver; +using static RealmWeaver.Analytics; -public partial class ModuleWeaver : Fody.BaseModuleWeaver, ILogger +// ReSharper disable once CheckNamespace +public class ModuleWeaver : Fody.BaseModuleWeaver, ILogger { public override void Execute() { @@ -55,66 +57,44 @@ public override IEnumerable GetAssembliesForScanning() yield return "System.Threading"; } - private Analytics.Config GetAnalyticsConfig(FrameworkName frameworkName) + private Config GetAnalyticsConfig(FrameworkName netFramework) { - var disableAnalytics = bool.TryParse(Config.Attribute("DisableAnalytics")?.Value, out var result) && result; - - var config = new Analytics.Config + AnalyticsCollection analyticsCollection; + if (Enum.TryParse(Config.Attribute("AnalyticsCollection")?.Value, out var collection)) { - Framework = "xamarin", // This is for backwards compatibility - RunAnalytics = !disableAnalytics, - }; - - config.FrameworkVersion = frameworkName.Version.ToString(); - config.TargetOSName = GetTargetOSName(frameworkName); - - // For backward compatibility - config.TargetOSVersion = frameworkName.Version.ToString(); - - return config; - } - - private static string GetTargetOSName(FrameworkName frameworkName) - { - try + analyticsCollection = collection; + } + else if (bool.TryParse(Config.Attribute("DisableAnalytics")?.Value, out var disableAnalytics)) { - // Legacy reporting used ios, osx, and android - switch (frameworkName.Identifier) - { - case "Xamarin.iOS": - return "ios"; - case "Xamarin.Mac": - return "osx"; - case "MonoAndroid": - case "Mono.Android": - return "android"; - } - - if (frameworkName.Identifier.EndsWith("-android", StringComparison.OrdinalIgnoreCase)) - { - return "android"; - } - - if (frameworkName.Identifier.EndsWith("-ios", StringComparison.OrdinalIgnoreCase)) - { - return "ios"; - } - - if (frameworkName.Identifier.EndsWith("-maccatalyst", StringComparison.OrdinalIgnoreCase)) - { - return "osx"; - } + analyticsCollection = disableAnalytics ? AnalyticsCollection.Disabled : AnalyticsCollection.Full; + } + else if (Environment.GetEnvironmentVariable("REALM_DISABLE_ANALYTICS") != null || Environment.GetEnvironmentVariable("CI") != null) + { + analyticsCollection = AnalyticsCollection.Disabled; } - catch + else { #if DEBUG - // Make sure we get build failures and address the problem in debug, - // but don't fail users' builds because of that. - throw; + analyticsCollection = AnalyticsCollection.DryRun; +#else + analyticsCollection = AnalyticsCollection.Full; #endif } - return "windows"; + var framework = AnalyticsUtils.GetFrameworkAndVersion(ModuleDefinition); + + return new() + { + AnalyticsCollection = analyticsCollection, + AnalyticsLogPath = Config.Attribute("AnalyticsLogPath")?.Value, + InstallationMethod = "Nuget", + NetFrameworkTarget = netFramework.Identifier, + NetFrameworkTargetVersion = netFramework.Version.ToString(), + TargetOSName = AnalyticsUtils.GetTargetOsName(netFramework), + FrameworkName = framework.Name, + FrameworkVersion = framework.Version, + Compiler = "msbuild", + }; } void ILogger.Debug(string message) @@ -127,12 +107,12 @@ void ILogger.Info(string message) WriteInfo(message); } - void ILogger.Error(string message, SequencePoint sequencePoint) + void ILogger.Error(string message, SequencePoint? sequencePoint) { WriteError(message, sequencePoint); } - void ILogger.Warning(string message, SequencePoint sequencePoint) + void ILogger.Warning(string message, SequencePoint? sequencePoint) { WriteWarning(message, sequencePoint); } diff --git a/Realm/Realm.Fody/Realm.Fody.csproj b/Realm/Realm.Fody/Realm.Fody.csproj index 10f8c8ff10..d319d1bdd4 100644 --- a/Realm/Realm.Fody/Realm.Fody.csproj +++ b/Realm/Realm.Fody/Realm.Fody.csproj @@ -10,10 +10,11 @@ Realm.Fody is a Fody weaver used to replace the property setters and getters of your Realm models with Realm-backed ones. $(ProjectDir)..\..\global.ruleset true - $(NoWarn),1591, NU5100, NU5128 - 8.0 + $(NoWarn),1591, NU5100, NU5128, CA1050 + 11.0 False $(DefineConstants);PRIVATE_INDEXTYPE + enable @@ -21,6 +22,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Realm/Realm.Fody/Realm.Fody.xcf b/Realm/Realm.Fody/Realm.Fody.xcf index 2f3d477bab..e147c2a8b4 100644 --- a/Realm/Realm.Fody/Realm.Fody.xcf +++ b/Realm/Realm.Fody/Realm.Fody.xcf @@ -1,8 +1,47 @@ - + + - Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics.cs + THIS IS DEPRECATED - USE `AnalyticsCollection` INSTEAD. Disables anonymized + usage information from being sent on build. Read more about what data is being collected and + why here: https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics.cs + + + + + Controls what anonymized usage information is being sent on build. Read more + about what data is being collected and why here: + https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics/Analytics.cs + + + + + + Analytics collection will run normally. This is the default behavior + and we hope you don't change it as the anonymized data collected is critical for + making the right decisions about the future of the Realm SDK. + + + + + Analytics collection will run but will not send it to the server. This + is useful in combination with `AnalyticsLogPath` if you want to review the data being + sent. + + + + + Analytics collection is disabled. No data will be sent on build. + + + + + + + + Controls where the payload for the anonymized metrics collection will be + stored. This can be useful if you want to review the data being collected by Realm. \ No newline at end of file diff --git a/Realm/Realm.PlatformHelpers/Platform.shared.cs b/Realm/Realm.PlatformHelpers/Platform.shared.cs index 5220c6f2ee..a4d2528731 100644 --- a/Realm/Realm.PlatformHelpers/Platform.shared.cs +++ b/Realm/Realm.PlatformHelpers/Platform.shared.cs @@ -17,6 +17,10 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; namespace Realms.PlatformHelpers { @@ -26,7 +30,7 @@ internal static class Platform private static IDeviceInfo? _deviceInfo; - private static Lazy _deviceInfoLazy = new(() => _deviceInfo ?? new DeviceInfo()); + private static readonly Lazy _deviceInfoLazy = new(() => _deviceInfo ?? new DeviceInfo()); public static IDeviceInfo DeviceInfo { @@ -41,5 +45,42 @@ public static IDeviceInfo DeviceInfo _deviceInfo = value; } } + + private static string? _bundleId; + public static string BundleId + { + get + { + var bundleId = _bundleId ?? Assembly.GetEntryAssembly()?.GetName().Name; + + if (bundleId == null) + { + // On Android, the entry assembly is null (there's no main() method), so we need to find + // the first assembly that has the ResourceDesignerAttribute with IsApplication = true. + var entryAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.CustomAttributes.Any(att => + att.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute" && + att.NamedArguments.FirstOrDefault(arg => arg.MemberName == "IsApplication").TypedValue.Value is bool isApplication && + isApplication)); + + bundleId = entryAssembly?.GetName().Name; + } + + return Sha256(bundleId); + } + set => _bundleId = value; + } + + internal static string Sha256(string? value) + { + if (value == null) + { + return Unknown; + } + + using var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(value)); + return Convert.ToBase64String(hash); + } } } diff --git a/Realm/Realm.SourceGenerator/InfoClasses.cs b/Realm/Realm.SourceGenerator/InfoClasses.cs index 4104f36ca1..2a15ae8ae7 100644 --- a/Realm/Realm.SourceGenerator/InfoClasses.cs +++ b/Realm/Realm.SourceGenerator/InfoClasses.cs @@ -58,7 +58,7 @@ internal record ClassInfo public bool HasDuplicatedName { get; set; } - public PropertyInfo PrimaryKey => Properties.FirstOrDefault(p => p.IsPrimaryKey); + public PropertyInfo? PrimaryKey => Properties.FirstOrDefault(p => p.IsPrimaryKey); } internal record NamespaceInfo @@ -282,7 +282,7 @@ public bool HasCorrectNullabilityAnnotation(bool ignoreObjectsNullability) return true; } - /** This method returns the type with the type string with correct nullability annotation wether they were enabled or not in the original file, + /** This method returns the type with the type string with correct nullability annotation whether they were enabled or not in the original file, * together with the same annotation for the internal type (for collections). In some cases we were just getting the internal type string on his own, and this would be wrong for some collections. * Simplified example from a generated unmanaged accessor: * @@ -306,7 +306,7 @@ public bool HasCorrectNullabilityAnnotation(bool ignoreObjectsNullability) } if (IsCollection && InternalType.NullableAnnotation == NullableAnnotation.None - && (InternalType.ScalarType == ScalarType.Data || InternalType.ScalarType == ScalarType.String)) + && (InternalType.ScalarType == ScalarType.Data || InternalType.ScalarType == ScalarType.String)) { return CollectionType switch { @@ -328,13 +328,8 @@ public bool HasCorrectNullabilityAnnotation(bool ignoreObjectsNullability) return ($"System.Collections.Generic.IDictionary", nullableInternalTypeString, true); } - var nullForgiving = false; - - if (NullableAnnotation != NullableAnnotation.Annotated && - (ScalarType == ScalarType.Data || ScalarType == ScalarType.String || ScalarType == ScalarType.Object || IsCollection)) - { - nullForgiving = true; - } + var nullForgiving = NullableAnnotation != NullableAnnotation.Annotated && + (IsCollection || ScalarType is ScalarType.Data or ScalarType.String or ScalarType.Object); return (CompleteFullyQualifiedString, internalTypeString, nullForgiving); } @@ -345,9 +340,7 @@ internal sealed record UnsupportedTypeInfo : PropertyTypeInfo public override bool IsUnsupported => true; } - internal abstract record CollectionTypeInfo : PropertyTypeInfo - { - } + internal abstract record CollectionTypeInfo : PropertyTypeInfo; internal record ListTypeInfo : CollectionTypeInfo { diff --git a/Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj b/Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj index f4267c3d76..6ada387c15 100644 --- a/Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj +++ b/Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj @@ -10,7 +10,7 @@ Realm.SourceGenerator is a Source Generator used to generate the implementation of Realm model classes. true 1701;1702;NU1701;NU5128 - 9.0 + 10.0 true true true @@ -20,13 +20,14 @@ enable true $(DefineConstants);PRIVATE_INDEXTYPE + Realms.SourceGenerator all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Realm/Realm.UnityUtils/Initializer.cs b/Realm/Realm.UnityUtils/Initializer.cs index d33bfa79f1..a646ce9160 100644 --- a/Realm/Realm.UnityUtils/Initializer.cs +++ b/Realm/Realm.UnityUtils/Initializer.cs @@ -34,6 +34,7 @@ public static void Initialize() if (Interlocked.CompareExchange(ref _isInitialized, 1, 0) == 0) { Platform.DeviceInfo = new UnityDeviceInfo(); + Platform.BundleId = Application.productName; InteropConfig.AddPotentialStorageFolder(FileHelper.GetStorageFolder()); Realms.Logging.Logger.Console = new UnityLogger(); Application.quitting += () => diff --git a/Realm/Realm.UnityWeaver/Realm.UnityWeaver.csproj b/Realm/Realm.UnityWeaver/Realm.UnityWeaver.csproj index 68282f29f4..655756eb34 100644 --- a/Realm/Realm.UnityWeaver/Realm.UnityWeaver.csproj +++ b/Realm/Realm.UnityWeaver/Realm.UnityWeaver.csproj @@ -9,8 +9,9 @@ $(ProjectDir)..\..\global.ruleset Realm.UnityWeaver 1701;1702;NU1701 - 8.0 + 11 $(DefineConstants);PRIVATE_INDEXTYPE + enable @@ -19,6 +20,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Realm/Realm.UnityWeaver/UnityWeaver.cs b/Realm/Realm.UnityWeaver/UnityWeaver.cs index 6b35aa6541..1a4f308cfa 100644 --- a/Realm/Realm.UnityWeaver/UnityWeaver.cs +++ b/Realm/Realm.UnityWeaver/UnityWeaver.cs @@ -27,10 +27,16 @@ using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.Compilation; +using UnityEditor.PackageManager; +using UnityEditor.PackageManager.Requests; using UnityEngine; +using static RealmWeaver.Analytics; +using CpuArchitecture = RealmWeaver.Metric.CpuArchitecture; +using OperatingSystem = RealmWeaver.Metric.OperatingSystem; using BindingFlags = System.Reflection.BindingFlags; +// ReSharper disable once CheckNamespace namespace RealmWeaver { // Heavily influenced by https://github.com/ExtendRealityLtd/Malimbe and https://github.com/fody/fody @@ -41,8 +47,9 @@ public class UnityWeaver : IPostBuildPlayerScriptDLLs, IPreprocessBuildWithRepor private const string WeaveEditorAssembliesPref = "realm_weave_editor_assemblies"; private const string WeaveEditorAssembliesMenuItemPath = "Tools/Realm/Process editor assemblies"; + private const string UnityPackageName = "io.realm.unity"; - private static readonly int _UnityMajorVersion = int.Parse(Application.unityVersion.Split('.')[0]); + private static readonly int UnityMajorVersion = int.Parse(Application.unityVersion.Split('.')[0]); private static bool _analyticsEnabled; @@ -58,6 +65,8 @@ private static bool AnalyticsEnabled } private static bool _weaveEditorAssemblies; + private static ListRequest? _listRequest; + private static TaskCompletionSource? _installMethodTask; private static bool WeaveEditorAssemblies { @@ -78,13 +87,28 @@ public static void Initialize() // We need to call that again after the editor is initialized to ensure that we populate the checkmark correctly. EditorApplication.delayCall += () => { + _listRequest = Client.List(); + + _installMethodTask = new TaskCompletionSource(); + + if (Application.isBatchMode) + { + // In batch mode, `update` won't get called until compilation is complete, + // which means we'll deadlock when we block compilation on the tcs completing + _installMethodTask.TrySetResult(Metric.Unknown()); + } + else + { + EditorApplication.update += OnEditorApplicationUpdate; + } + AnalyticsEnabled = EditorPrefs.GetBool(EnableAnalyticsPref, defaultValue: true); WeaveEditorAssemblies = EditorPrefs.GetBool(WeaveEditorAssembliesPref, defaultValue: false); WeaverAssemblyResolver.ApplicationDataPath = Application.dataPath; WeaveAssembliesOnEditorLaunch(); }; - CompilationPipeline.assemblyCompilationFinished += (string assemblyPath, CompilerMessage[] _) => + CompilationPipeline.assemblyCompilationFinished += (assemblyPath, _) => { if (string.IsNullOrEmpty(assemblyPath)) { @@ -98,10 +122,36 @@ public static void Initialize() return; } - WeaveAssemblyCore(assemblyPath, assembly.allReferences, "Unity Editor", GetTargetOSName(Application.platform)); + var config = GetAnalyticsConfig(); + WeaveAssemblyCore(assemblyPath, assembly.allReferences, config); }; } + private static void OnEditorApplicationUpdate() + { + if (_listRequest?.IsCompleted != true) + { + return; + } + + EditorApplication.update -= OnEditorApplicationUpdate; + + var installMethod = Metric.Unknown(); + + if (_listRequest.Status == StatusCode.Success) + { + var realmPackage = _listRequest.Result.FirstOrDefault(p => p.name == UnityPackageName); + installMethod = realmPackage?.source switch + { + PackageSource.LocalTarball => "Manual", + PackageSource.Registry => "NPM", + _ => Metric.Unknown(realmPackage?.source.ToString()), + }; + } + + _installMethodTask!.SetResult(installMethod); + } + [MenuItem("Tools/Realm/Weave Assemblies")] public static async void WeaveAllAssembliesMenuItem() { @@ -131,14 +181,14 @@ public static void WeaveEditorAssembliesMenuItem() private static void WeaveAssembliesOnEditorLaunch() { // This code is susceptible to the year 2038 problem. Refactor before 2037! - const string AutomaticWeavePrefKey = "realm_last_automatic_weave"; - var lastAutomaticWeave = EditorPrefs.GetInt(AutomaticWeavePrefKey, 0); + const string automaticWeavePrefKey = "realm_last_automatic_weave"; + var lastAutomaticWeave = EditorPrefs.GetInt(automaticWeavePrefKey, 0); var timeSinceLastWeave = (DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(lastAutomaticWeave)).TotalSeconds; if (timeSinceLastWeave > EditorApplication.timeSinceStartup) { // We haven't executed the automatic weaver in this editor session _ = WeaveAllAssemblies(); - EditorPrefs.SetInt(AutomaticWeavePrefKey, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + EditorPrefs.SetInt(automaticWeavePrefKey, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()); } } @@ -151,16 +201,19 @@ private static async Task WeaveAllAssemblies() EditorApplication.LockReloadAssemblies(); var assembliesToWeave = GetAssemblies(); var weaveResults = new List(); + + var config = GetAnalyticsConfig(); + await Task.Run(() => { foreach (var assembly in assembliesToWeave) { - if (!WeaveAssemblyCore(assembly.outputPath, assembly.allReferences, "Unity Editor", GetTargetOSName(Application.platform))) + if (!WeaveAssemblyCore(assembly.outputPath, assembly.allReferences, config)) { continue; } - string sourceFilePath = assembly.sourceFiles.FirstOrDefault(); + var sourceFilePath = assembly.sourceFiles.FirstOrDefault(); if (sourceFilePath == null) { continue; @@ -192,7 +245,7 @@ await Task.Run(() => return assembliesWoven; } - private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable references, string framework, string targetOSName) + private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable references, Config analyticsConfig) { var name = Path.GetFileNameWithoutExtension(assemblyPath); @@ -213,14 +266,6 @@ private static bool WeaveAssemblyCore(string assemblyPath, IEnumerable r // using Mono, so we just hardcode Unity which is treated as Mono/.NET Framework by the weaver. var weaver = new Weaver(resolutionResult.Module, UnityLogger.Instance, "Unity"); - var analyticsConfig = new Analytics.Config - { - TargetOSName = targetOSName, - FrameworkVersion = Application.unityVersion, - Framework = framework, - RunAnalytics = AnalyticsEnabled - }; - var results = weaver.Execute(analyticsConfig); if (results.ErrorMessage != null) @@ -263,32 +308,42 @@ public void OnPostBuildPlayerScriptDLLs(BuildReport report) .ToArray(); var assembliesToWeave = files.Where(f => f.role == "ManagedLibrary"); - var targetOS = GetTargetOSName(report.summary.platform); + var config = GetAnalyticsConfig(report.summary.platform); + foreach (var file in assembliesToWeave) { - WeaveAssemblyCore(file.path, referencePaths, "Unity", targetOS); + WeaveAssemblyCore(file.path, referencePaths, config); + } + + if (report.summary.platform != BuildTarget.iOS && report.summary.platform != BuildTarget.tvOS) + { + return; } - if (report.summary.platform == BuildTarget.iOS || report.summary.platform == BuildTarget.tvOS) + var realmAssemblyPath = files + .SingleOrDefault(r => "Realm.dll".Equals(Path.GetFileName(r.path), StringComparison.OrdinalIgnoreCase)) + .path; + + var realmResolutionResult = WeaverAssemblyResolver.Resolve(realmAssemblyPath, referencePaths); + if (realmResolutionResult == null) { - var realmAssemblyPath = files - .SingleOrDefault(r => "Realm.dll".Equals(Path.GetFileName(r.path), StringComparison.OrdinalIgnoreCase)) - .path; + return; + } - var realmResolutionResult = WeaverAssemblyResolver.Resolve(realmAssemblyPath, referencePaths); - using (realmResolutionResult) + using (realmResolutionResult) + { + var wrappersReference = realmResolutionResult.Module.ModuleReferences.SingleOrDefault(r => r.Name == "realm-wrappers"); + if (wrappersReference == null) { - var wrappersReference = realmResolutionResult.Module.ModuleReferences.SingleOrDefault(r => r.Name == "realm-wrappers"); - if (wrappersReference != null) - { - wrappersReference.Name = "__Internal"; - realmResolutionResult.SaveModuleUpdates(); - } + return; } + + wrappersReference.Name = "__Internal"; + realmResolutionResult.SaveModuleUpdates(); } } - public void OnPostprocessBuild(BuildReport report) + public void OnPostprocessBuild(BuildReport? report) { switch (report?.summary.platform) { @@ -299,7 +354,7 @@ public void OnPostprocessBuild(BuildReport report) } } - public void OnPreprocessBuild(BuildReport report) + public void OnPreprocessBuild(BuildReport? report) { bool enableForDevice; bool enableForSimulator; @@ -323,21 +378,21 @@ public void OnPreprocessBuild(BuildReport report) /// /// Updates the native module import config for the wrappers framework. Unity doesn't support /// xcframework, which means that it won't correctly include it when building for iOS. This is - /// a somewhat hacky solution that will manually update the compatibilify flag and the AddToEmbeddedBinaries + /// a somewhat hacky solution that will manually update the compatibility flag and the AddToEmbeddedBinaries /// flag just for the slice that is compatible with the current build target (simulator or device). /// private static void UpdateiOSFrameworks(bool enableForDevice, bool enableForSimulator, BuildTarget buildTarget) { - const string ErrorMessage = "Failed to find the native Realm framework at '{0}'. " + + const string errorMessage = "Failed to find the native Realm framework at '{0}'. " + "Please double check that you have imported Realm correctly and that the file exists. " + "Typically, it should be located at Packages/io.realm.unity/Runtime/{1}"; - const string SimulatorPath = "Simulator"; - const string DevicePath = "Device"; + const string simulatorPath = "Simulator"; + const string devicePath = "Device"; var importers = PluginImporter.GetAllImporters(); - UpdateiOSFramework(SimulatorPath, enableForSimulator); - UpdateiOSFramework(DevicePath, enableForDevice); + UpdateiOSFramework(simulatorPath, enableForSimulator); + UpdateiOSFramework(devicePath, enableForDevice); void UpdateiOSFramework(string path, bool enabled) { @@ -345,7 +400,7 @@ void UpdateiOSFramework(string path, bool enabled) var frameworkImporter = importers.SingleOrDefault(i => i.assetPath.Contains(path)); if (frameworkImporter == null) { - throw new Exception(string.Format(ErrorMessage, path, buildTarget)); + throw new Exception(string.Format(errorMessage, path, buildTarget)); } frameworkImporter.SetCompatibleWithPlatform(buildTarget, enabled); @@ -363,59 +418,200 @@ private static Assembly[] GetAssemblies() return CompilationPipeline.GetAssemblies(AssembliesType.Player); } - private static string GetTargetOSName(BuildTarget target) + private static string GetTargetOSName(BuildTarget? target) { - // These have to match Analytics.GetConfig(FrameworkName) - switch (target) + // target is null for editor builds - in that case, we return the current OS + // as target. + if (target == null) { - case BuildTarget.StandaloneOSX: - return "osx"; - case BuildTarget.StandaloneWindows: - case BuildTarget.StandaloneWindows64: - return "windows"; - case BuildTarget.iOS: - return "ios"; - case BuildTarget.Android: - return "android"; - case BuildTarget.StandaloneLinux64: - return "linux"; - case BuildTarget.tvOS: - return "tvos"; - default: - return "UNKNOWN"; + return Application.platform switch + { + RuntimePlatform.WindowsEditor => OperatingSystem.Windows, + RuntimePlatform.OSXEditor => OperatingSystem.MacOS, + RuntimePlatform.LinuxEditor => OperatingSystem.Linux, + _ => Metric.Unknown(Application.platform.ToString()), + }; } + + // These have to match Analytics.GetConfig(FrameworkName) + return target switch + { + BuildTarget.StandaloneOSX => OperatingSystem.MacOS, + BuildTarget.StandaloneWindows or BuildTarget.StandaloneWindows64 or BuildTarget.WSAPlayer => OperatingSystem.Windows, + BuildTarget.iOS => OperatingSystem.Ios, + BuildTarget.Android => OperatingSystem.Android, + BuildTarget.StandaloneLinux64 => OperatingSystem.Linux, + BuildTarget.tvOS => OperatingSystem.TvOs, + BuildTarget.XboxOne => OperatingSystem.XboxOne, + _ => Metric.Unknown(target.ToString()), + }; } - private static string GetTargetOSName(RuntimePlatform target) => target switch + private static string GetTargetOsVersion(BuildTarget? target) { - RuntimePlatform.WindowsEditor => "windows", - RuntimePlatform.OSXEditor => "osx", - RuntimePlatform.LinuxEditor => "linux", - _ => "UNKOWN", - }; + // target is null for editor builds - in that case, we return the host OS + // version. + if (target == null) + { + return Environment.OSVersion.Version.ToString(); + } + + return target switch + { + BuildTarget.Android => ((int)PlayerSettings.Android.targetSdkVersion).ToString(), + BuildTarget.iOS => PlayerSettings.iOS.targetOSVersionString, + BuildTarget.tvOS => PlayerSettings.tvOS.targetOSVersionString, + _ => Metric.Unknown(), + }; + } private static BuildFile[] GetFiles(BuildReport report) { try { - if (_UnityMajorVersion < 2022) + if (UnityMajorVersion < 2022) { - var getFilesPI = typeof(BuildReport).GetProperty("files", BindingFlags.Public | BindingFlags.Instance); + var getFilesPI = typeof(BuildReport).GetProperty("files", BindingFlags.Public | BindingFlags.Instance)!; return (BuildFile[])getFilesPI.GetValue(report); } // Starting with 2022, BuildReport.files is replaced with BuildReport.GetFiles. This is a // bit hacky, but allows us to target both versions with the same assembly. - var getFilesMI = typeof(BuildReport).GetMethod("GetFiles", BindingFlags.Public | BindingFlags.Instance); + var getFilesMI = typeof(BuildReport).GetMethod("GetFiles", BindingFlags.Public | BindingFlags.Instance)!; return (BuildFile[])getFilesMI.Invoke(report, null); } catch (Exception e) { - UnityLogger.Instance.Error($"Failed to obtain list of files from report. Please report this error on http://github.com/realm/realm-dotnet/issues. Unity version: {Application.unityVersion}, error message: {e.Message}."); + UnityLogger.Instance.Error($"Failed to obtain list of files from report. Please report this error on https://github.com/realm/realm-dotnet/issues. Unity version: {Application.unityVersion}, error message: {e.Message}."); throw; } } + private static string GetMinimumOsVersion(BuildTarget? target) + { + // target is null for editor builds - in that case, we return the host OS + // version. + if (target == null) + { + return Environment.OSVersion.Version.ToString(); + } + + return target switch + { + BuildTarget.Android => ((int)PlayerSettings.Android.minSdkVersion).ToString(), + BuildTarget.iOS => PlayerSettings.iOS.targetOSVersionString, + BuildTarget.tvOS => PlayerSettings.tvOS.targetOSVersionString, + _ => Metric.Unknown(), + }; + } + + private static Config GetAnalyticsConfig(BuildTarget? target = null) + { + var netFrameworkInfo = GetNetFrameworkInfo(target); + var compiler = PlayerSettings.GetScriptingBackend(BuildPipeline.GetBuildTargetGroup(target ?? EditorUserBuildSettings.activeBuildTarget)).ToString(); + + var analyticsEnabled = AnalyticsEnabled && + Environment.GetEnvironmentVariable("REALM_DISABLE_ANALYTICS") == null && + Environment.GetEnvironmentVariable("CI") == null; + + return new Config + { + TargetOSName = GetTargetOSName(target), + Compiler = compiler, + NetFrameworkTarget = netFrameworkInfo.Name, + NetFrameworkTargetVersion = netFrameworkInfo.Version, + AnalyticsCollection = analyticsEnabled ? AnalyticsCollection.Full : AnalyticsCollection.Disabled, + InstallationMethod = _installMethodTask!.Task.Wait(1000) ? _installMethodTask.Task.Result : Metric.Unknown(), + FrameworkName = target == null ? Metric.Framework.UnityEditor : Metric.Framework.Unity, + FrameworkVersion = Application.unityVersion, + TargetArchitecture = GetCpuArchitecture(target), + TargetOsVersion = GetTargetOsVersion(target), + TargetOsMinimumVersion = GetMinimumOsVersion(target), + ProjectId = PlayerSettings.productName, + }; + } + + private static (string Name, string Version) GetNetFrameworkInfo(BuildTarget? buildTarget) + { + var targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget ?? EditorUserBuildSettings.activeBuildTarget); + var apiTarget = PlayerSettings.GetApiCompatibilityLevel(targetGroup); + + // these consts are exactly mapped to what .NET reports in any .NET application + const string netStandardApi = ".NETStandard"; + const string netFrameworkApi = ".NETFramework"; + + var unityVersion = new Version(Application.unityVersion.Substring(0, 6)); + + // conversion necessary as after unity version 2021.1, entry NET_4_6 and NET_Standard_2_0 + // are actually representing .NET 4.8 and .NET Standard 2.1 + // https://github.com/Unity-Technologies/UnityCsReference/blob/664dfe30cee8ee2ef7dd8c5e9db6235915245ecb/Editor/Mono/PlayerSettings.bindings.cs#L158 + if (unityVersion >= new Version("2021.2")) + { + if (apiTarget == ApiCompatibilityLevel.NET_Standard_2_0) + { + return (netStandardApi, "2.1"); + } + + if (apiTarget == ApiCompatibilityLevel.NET_4_6) + { + return (netFrameworkApi, "4.8"); + } + } + + if (apiTarget == ApiCompatibilityLevel.NET_Standard_2_0) + { + return (netStandardApi, "2.0"); + } + + if (apiTarget == ApiCompatibilityLevel.NET_4_6) + { + return (netFrameworkApi, "4.6"); + } + + // this should really never be the case + return (apiTarget.ToString(), ""); + } + + private static string GetCpuArchitecture(BuildTarget? buildTarget) + { + // buildTarget is null when we're building for the editor + if (buildTarget == null) + { + if (SystemInfo.processorType.IndexOf("ARM", StringComparison.OrdinalIgnoreCase) > -1) + { + return Environment.Is64BitProcess ? CpuArchitecture.Arm64 : CpuArchitecture.Arm; + } + + // Must be in the x86 family. + return Environment.Is64BitProcess ? CpuArchitecture.X64 : CpuArchitecture.X86; + } + + return buildTarget switch + { + BuildTarget.iOS or BuildTarget.tvOS => CpuArchitecture.Arm64, + BuildTarget.StandaloneOSX => EditorUserBuildSettings.GetPlatformSettings(BuildPipeline.GetBuildTargetName(buildTarget.Value), "Architecture") switch + { + "ARM64" => CpuArchitecture.Arm64, + "x64" => CpuArchitecture.X64, + _ => CpuArchitecture.Universal, + }, + BuildTarget.StandaloneWindows => CpuArchitecture.X86, + BuildTarget.Android => PlayerSettings.Android.targetArchitectures switch + { + AndroidArchitecture.ARMv7 => CpuArchitecture.Arm, + AndroidArchitecture.ARM64 => CpuArchitecture.Arm64, + + // These two don't have enum values in our Unity reference dll, but exist in newer versions + // See https://github.com/Unity-Technologies/UnityCsReference/blob/70abf502c521c169ee8a302aa48c5600fc7c39fc/Editor/Mono/PlayerSettingsAndroid.bindings.cs#L14 + (AndroidArchitecture)(1 << 2) => CpuArchitecture.X86, + (AndroidArchitecture)(1 << 3) => CpuArchitecture.X64, + _ => CpuArchitecture.Universal, + }, + BuildTarget.StandaloneWindows64 or BuildTarget.StandaloneLinux64 or BuildTarget.XboxOne => CpuArchitecture.X64, + _ => Metric.Unknown(), + }; + } + private class UnityLogger : ILogger { public static UnityLogger Instance { get; } = new UnityLogger(); @@ -425,7 +621,7 @@ public void Debug(string message) System.Diagnostics.Debug.WriteLine(message); } - public void Error(string message, SequencePoint sequencePoint = null) + public void Error(string message, SequencePoint? sequencePoint = null) { UnityEngine.Debug.LogError(GetMessage(message, sequencePoint)); } @@ -435,19 +631,14 @@ public void Info(string message) UnityEngine.Debug.Log(message); } - public void Warning(string message, SequencePoint sequencePoint = null) + public void Warning(string message, SequencePoint? sequencePoint = null) { UnityEngine.Debug.LogWarning(GetMessage(message, sequencePoint)); } - private static string GetMessage(string message, SequencePoint sp) + private static string GetMessage(string message, SequencePoint? sp) { - if (sp == null) - { - return message; - } - - return $"{sp.Document.Url}({sp.StartLine}, {sp.StartColumn}): {message}"; + return sp == null ? message : $"{sp.Document.Url}({sp.StartLine}, {sp.StartColumn}): {message}"; } } } diff --git a/Realm/Realm.UnityWeaver/WeaverAssemblyResolver.cs b/Realm/Realm.UnityWeaver/WeaverAssemblyResolver.cs index fb236487a3..88bf88cdfc 100644 --- a/Realm/Realm.UnityWeaver/WeaverAssemblyResolver.cs +++ b/Realm/Realm.UnityWeaver/WeaverAssemblyResolver.cs @@ -23,6 +23,7 @@ using Mono.Cecil; using Mono.Cecil.Pdb; +// ReSharper disable once CheckNamespace namespace RealmWeaver { public class ResolutionResult : IDisposable @@ -63,7 +64,7 @@ public class WeaverAssemblyResolver : BaseAssemblyResolver { private readonly IDictionary _cache = new Dictionary(); - public static string ApplicationDataPath { get; set; } + public static string ApplicationDataPath { get; set; } = null!; private WeaverAssemblyResolver(IEnumerable references) { @@ -74,7 +75,7 @@ private WeaverAssemblyResolver(IEnumerable references) } } - public static ResolutionResult Resolve(string assemblyPath, IEnumerable references) + public static ResolutionResult? Resolve(string? assemblyPath, IEnumerable references) { if (assemblyPath == null) { @@ -105,6 +106,7 @@ public static ResolutionResult Resolve(string assemblyPath, IEnumerable } catch { + // ignored } return new ResolutionResult(module, absolutePath, resolver, hasDebugInfo); @@ -120,7 +122,7 @@ private static string GetAbsolutePath(string assemblyPath) return Path.Combine(ApplicationDataPath, "..", assemblyPath); } - public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + public override AssemblyDefinition? Resolve(AssemblyNameReference name, ReaderParameters parameters) { if (name == null) { @@ -136,6 +138,7 @@ public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderPar } catch { + // ignored } } diff --git a/Realm/Realm.Weaver/Analytics.cs b/Realm/Realm.Weaver/Analytics.cs deleted file mode 100644 index 532cc332e6..0000000000 --- a/Realm/Realm.Weaver/Analytics.cs +++ /dev/null @@ -1,208 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -using System; -using System.Linq; -using System.Net.NetworkInformation; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; - -namespace RealmWeaver -{ - // Asynchronously submits build information to Realm when the assembly weaver - // is running - // - // To be clear: this does *not* run when your app is in production or on - // your end-user's devices; it will only run when you build your app from source. - // - // Why are we doing this? Because it helps us build a better product for you. - // None of the data personally identifies you, your employer or your app, but it - // *will* help us understand what Realm version you use, what host OS you use, - // etc. Having this info will help with prioritizing our time, adding new - // features and deprecating old features. Collecting an anonymized assembly name & - // anonymized MAC is the only way for us to count actual usage of the other - // metrics accurately. If we don't have a way to deduplicate the info reported, - // it will be useless, as a single developer building their app on Windows ten - // times would report 10 times more than a single developer that only builds - // once from Mac OS X, making the data all but useless. No one likes sharing - // data unless it's necessary, we get it, and we've debated adding this for a - // long long time. Since Realm is a free product without an email sign-up, we - // feel this is a necessary step so we can collect relevant data to build a - // better product for you. - // - // Currently the following information is reported: - // - What version of Realm is being used - // - What OS you are running on - // - What OS you are building for - // - An anonymized MAC address and assembly name ID to aggregate the other information on. - internal class Analytics - { - private const string JsonTemplate = @"{ - ""event"": ""Run"", - ""properties"": { - ""token"": ""ce0fac19508f6c8f20066d345d360fd0"", - ""distinct_id"": ""%USER_ID%"", - ""Anonymized MAC Address"": ""%USER_ID%"", - ""Anonymized Bundle ID"": ""%APP_ID%"", - ""Binding"": ""dotnet"", - ""Language"": ""c#"", - ""Framework"": ""%FRAMEWORK%"", - ""Framework Version"": ""%FRAMEWORK_VERSION%"", - ""Sync Enabled"": ""%SYNC_ENABLED%"", - ""Realm Version"": ""%REALM_VERSION%"", - ""Host OS Type"": ""%OS_TYPE%"", - ""Host OS Version"": ""%OS_VERSION%"", - ""Target OS Type"": ""%TARGET_OS%"", - ""Target OS Version"": ""%TARGET_OS_VERSION%"" - } -}"; - - private readonly Config _config; - - private static string AnonymizedUserID - { - get - { - try - { - var id = GenerateComputerIdentifier(); - return id != null ? SHA256Hash(id) : "UNKNOWN"; - } - catch - { - return "UNKNOWN"; - } - } - } - - private static byte[] GenerateComputerIdentifier() - { - // Assume OS X if not Windows. - return NetworkInterface.GetAllNetworkInterfaces() - .Where(n => n.Name == "en0" || (n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback)) - .Select(n => n.GetPhysicalAddress().GetAddressBytes()) - .FirstOrDefault(); - } - - private string JsonPayload - { - get - { - ComputeHostOSNameAndVersion(out var osName, out var osVersion); - return JsonTemplate - .Replace("%USER_ID%", AnonymizedUserID) - .Replace("%APP_ID%", _config.ModuleName) - - .Replace("%SYNC_ENABLED%", _config.IsUsingSync.ToString()) - - // Version of weaver is expected to match that of the library. - .Replace("%REALM_VERSION%", Assembly.GetExecutingAssembly().GetName().Version.ToString()) - - .Replace("%OS_TYPE%", osName) - .Replace("%OS_VERSION%", osVersion) - .Replace("%TARGET_OS%", _config.TargetOSName) - .Replace("%TARGET_OS_VERSION%", _config.TargetOSVersion) - .Replace("%FRAMEWORK%", _config.Framework) - .Replace("%FRAMEWORK_VERSION%", _config.FrameworkVersion); - } - } - - internal Analytics(Config config) - { - config.ModuleName = SHA256Hash(Encoding.UTF8.GetBytes(config.ModuleName)); - _config = config; - } - - internal string SubmitAnalytics() - { - if (!_config.RunAnalytics || - Environment.GetEnvironmentVariable("REALM_DISABLE_ANALYTICS") != null || - Environment.GetEnvironmentVariable("CI") != null) - { - return "Analytics disabled"; - } - - var payload = JsonPayload; - - // uncomment next line to inspect the payload under Windows VS build - // Debugger.Launch(); -#if !DEBUG - var base64Payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload)); - - SendRequest( - "https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/metric_webhook/metric?data=", - base64Payload, - string.Empty); -#endif - - return payload; - } - - private static void SendRequest(string prefixAddr, string payload, string suffixAddr) - { - var request = System.Net.HttpWebRequest.CreateHttp(new Uri(prefixAddr + payload + suffixAddr)); - request.Method = "GET"; - request.Timeout = 4000; - request.ReadWriteTimeout = 2000; - request.GetResponse(); - } - - private static string SHA256Hash(byte[] bytes) - { - using (var sha256 = System.Security.Cryptography.SHA256.Create()) - { - return BitConverter.ToString(sha256.ComputeHash(bytes)); - } - } - - private static void ComputeHostOSNameAndVersion(out string name, out string version) - { - var platformRegex = new Regex("^(?[^0-9]*) (?[^ ]*)", RegexOptions.Compiled); - var osDescription = platformRegex.Match(RuntimeInformation.OSDescription); - if (osDescription.Success) - { - name = osDescription.Groups["platform"].Value; - version = osDescription.Groups["version"].Value; - } - else - { - name = Environment.OSVersion.Platform.ToString(); - version = Environment.OSVersion.VersionString; - } - } - - public class Config - { - public bool RunAnalytics { get; set; } - - public string TargetOSName { get; set; } - - public string TargetOSVersion { get; set; } - - public string Framework { get; set; } - - public bool IsUsingSync { get; set; } - - public string ModuleName { get; set; } - - public string FrameworkVersion { get; set; } - } - } -} diff --git a/Realm/Realm.Weaver/Analytics/Analytics.cs b/Realm/Realm.Weaver/Analytics/Analytics.cs new file mode 100644 index 0000000000..04200f2937 --- /dev/null +++ b/Realm/Realm.Weaver/Analytics/Analytics.cs @@ -0,0 +1,569 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Mono.Cecil; +using Mono.Cecil.Cil; +using static RealmWeaver.AnalyticsUtils; + +using Feature = RealmWeaver.Metric.Feature; +using UserEnvironment = RealmWeaver.Metric.Environment; + +namespace RealmWeaver +{ + // Asynchronously submits build information to Realm when the assembly weaver + // is running + // + // To be clear: this does *not* run when your app is in production or on + // your end-user's devices; it will only run when you build your app from source. + // + // Why are we doing this? Because it helps us build a better product for you. + // None of the data personally identifies you, your employer or your app, but it + // *will* help us understand what Realm version you use, what host OS you use, + // etc. Having this info will help with prioritizing our time, adding new + // features and deprecating old ones. Collecting an anonymized assembly name & + // anonymized MAC is the only way for us to count actual usage of the other + // metrics accurately. If we don't have a way to deduplicate the info reported, + // it will be useless, as a single developer building their app on Windows ten + // times would report 10 times more than a single developer that only builds + // once from Mac OS X, making the data all but useless. No one likes sharing + // data unless it's necessary, we get it, and we've debated adding this for a + // long long time. Since Realm is a free product without an email sign-up, we + // feel this is a necessary step so we can collect relevant data to build a + // better product for you. + // + // Currently the following information is reported: + // - What OS and CPU architecture you are running on + // - What OS and CPU architecture you are building for + // - What version of the Realm SDK you're using + // - What framework and what framework version Realm is being used with (e.g. Xamarin, MAUI, etc.) + // - How the Realm SDK was installed (e.g. Nuget, manual, etc.) + // - What APIs of the Realm SDK you're using + // - An anonymized identifier and assembly name ID to aggregate the other information on. + internal class Analytics + { + // The value of this field is modified by CI in the "prepare-release" action, so do not change its name. + private const string CoreVersion = "13.15.0"; + + private readonly ImportedReferences _references; + private readonly ILogger _logger; + + private readonly Dictionary _realmEnvMetrics = new(); + private readonly Dictionary _realmFeaturesToAnalyze; + + private readonly Dictionary> _apiAnalysisSetters; + + private readonly Dictionary> _classAnalysisSetters; + + private readonly Config _config; + + private readonly Task _analyzeUserAssemblyTask; + + public Analytics(Config config, ImportedReferences references, ILogger logger, ModuleDefinition module) + { + _config = config; + _references = references; + _logger = logger; + + if (config.AnalyticsCollection == AnalyticsCollection.Disabled) + { + _realmFeaturesToAnalyze = new(); + _classAnalysisSetters = new(); + _apiAnalysisSetters = new(); + _analyzeUserAssemblyTask = Task.CompletedTask; + return; + } + + _realmFeaturesToAnalyze = Metric.SdkFeatures.Keys.ToDictionary(c => c, _ => (byte)0); + + _classAnalysisSetters = new() + { + ["Class"] = member => + member is PropertyDefinition property && property.PropertyType.IsAnyRealmObject(_references) ? + new(true, Feature.RealmObjectReference) : default, + [Feature.RealmValue] = _ => new(true, Feature.RealmValue), + ["IList`1"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveList, Feature.ReferenceList), + ["IDictionary`2"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveDictionary, Feature.ReferenceDictionary, 1), + ["ISet`1"] = member => AnalyzeCollectionProperty(member, Feature.PrimitiveSet, Feature.ReferenceSet), + ["RealmInteger`1"] = _ => new(true, Feature.RealmInteger), + [Feature.BacklinkAttribute] = _ => new(true, Feature.BacklinkAttribute) + }; + + _apiAnalysisSetters = new() + { + [Feature.GetInstanceAsync] = instruction => AnalyzeRealmApi(instruction, Feature.GetInstanceAsync), + [Feature.GetInstance] = instruction => AnalyzeRealmApi(instruction, Feature.GetInstance), + [Feature.Find] = instruction => AnalyzeRealmApi(instruction, Feature.Find), + [Feature.WriteAsync] = instruction => AnalyzeRealmApi(instruction, Feature.WriteAsync), + [Feature.ThreadSafeReference] = instruction => AnalyzeRealmApi(instruction, Feature.ThreadSafeReference), + + // check if it's the right signature, that is 2 params in total of which + // the second a bool and that it's set to true. + [Feature.Add] = instruction => + IsInRealmNamespace(instruction.Operand) && + instruction.Operand is MethodSpecification methodSpecification && + methodSpecification.Parameters.Count == 2 && + methodSpecification.Parameters[1].ParameterType.MetadataType == MetadataType.Boolean && + instruction.Previous.OpCode == OpCodes.Ldc_I4_1 ? + new(true, Feature.Add) : default, + [Feature.ShouldCompactOnLaunch] = _ => new(true, Feature.ShouldCompactOnLaunch), + [Feature.MigrationCallback] = _ => new(true, Feature.MigrationCallback), + [Feature.RealmChanged] = _ => new(true, Feature.RealmChanged), + ["SubscribeForNotifications"] = instruction => + { + if (instruction.Operand is not MethodSpecification methodSpecification || !IsInRealmNamespace(instruction.Operand)) + { + return default; + } + + var collectionType = ((TypeSpecification)methodSpecification.Parameters[0].ParameterType).Name; + var key = collectionType switch + { + "IQueryable`1" or "IOrderedQueryable`1" => Feature.ResultSubscribeForNotifications, + "IList`1" => Feature.ListSubscribeForNotifications, + "ISet`1" => Feature.SetSubscribeForNotifications, + "IDictionary`2" => Feature.DictionarySubscribeForNotifications, + _ => $"{collectionType} unknown collection" + }; + + var shouldDelete = ContainsAllRelatedFeatures(key, + Feature.ResultSubscribeForNotifications, + Feature.ListSubscribeForNotifications, + Feature.SetSubscribeForNotifications, + Feature.DictionarySubscribeForNotifications); + + return new(shouldDelete, key); + }, + ["PropertyChanged"] = instruction => + { + string? key = null; + if (instruction.Operand is MemberReference reference) + { + if (reference.DeclaringType.IsAnyRealmObject(_references)) + { + key = Feature.ObjectNotification; + } + else if (reference.DeclaringType.IsSameAs(_references.SyncSession)) + { + key = Feature.ConnectionNotification; + } + } + + if (key == null) + { + return default; + } + + var shouldDelete = ContainsAllRelatedFeatures(key, Feature.ObjectNotification, Feature.ConnectionNotification); + return new(shouldDelete, key); + }, + [Feature.RecoverOrDiscardUnsyncedChangesHandler] = _ => new(true, Feature.RecoverOrDiscardUnsyncedChangesHandler), + [Feature.RecoverUnsyncedChangesHandler] = _ => new(true, Feature.RecoverUnsyncedChangesHandler), + [Feature.DiscardUnsyncedChangesHandler] = _ => new(true, Feature.DiscardUnsyncedChangesHandler), + [Feature.ManualRecoveryHandler] = _ => new(true, Feature.ManualRecoveryHandler), + [Feature.GetProgressObservable] = _ => new(true, Feature.GetProgressObservable), + [Feature.PartitionSyncConfiguration] = _ => new(true, Feature.PartitionSyncConfiguration), + [Feature.FlexibleSyncConfiguration] = _ => new(true, Feature.FlexibleSyncConfiguration), + [Feature.Anonymous] = instruction => AnalyzeRealmApi(instruction, Feature.Anonymous), + [Feature.EmailPassword] = instruction => AnalyzeRealmApi(instruction, Feature.EmailPassword), + [Feature.Facebook] = instruction => AnalyzeRealmApi(instruction, Feature.Facebook), + [Feature.Google] = instruction => AnalyzeRealmApi(instruction, Feature.Google), + [Feature.Apple] = instruction => AnalyzeRealmApi(instruction, Feature.Apple), + [Feature.JWT] = instruction => AnalyzeRealmApi(instruction, Feature.JWT), + [Feature.ApiKey] = instruction => AnalyzeRealmApi(instruction, Feature.ApiKey), + [Feature.Function] = instruction => AnalyzeRealmApi(instruction, Feature.Function), + [Feature.CallAsync] = instruction => AnalyzeRealmApi(instruction, Feature.CallAsync), + [Feature.GetMongoClient] = _ => new(true, Feature.GetMongoClient), + [Feature.DynamicApi] = _ => new(true, Feature.DynamicApi) + }; + + _analyzeUserAssemblyTask = Task.Run(() => + { + AnalyzeUserAssembly(module); + }); + + FeatureAnalysisResult AnalyzeCollectionProperty(IMemberDefinition member, string primitiveKey, string referenceKey, int genericArgIndex = 0) + { + if (member is not PropertyDefinition property || + property.PropertyType is not GenericInstanceType genericType || + genericType.GenericArguments.Count < genericArgIndex + 1) + { + return default; + } + + var keyToAdd = genericType.GenericArguments[genericArgIndex].IsPrimitive ? + primitiveKey : referenceKey; + + var shouldDelete = ContainsAllRelatedFeatures(keyToAdd, referenceKey, primitiveKey); + return new(shouldDelete, keyToAdd); + } + + FeatureAnalysisResult AnalyzeRealmApi(Instruction instruction, string key) + { + if (IsInRealmNamespace(instruction.Operand)) + { + return new(true, key); + } + + return default; + } + + bool ContainsAllRelatedFeatures(string key, params string[] features) + { + foreach (var feature in features) + { + if (feature != key && (!_realmFeaturesToAnalyze.TryGetValue(feature, out var value) || value == 0)) + { + return false; + } + } + + return true; + } + } + + private void AnalyzeUserAssembly(ModuleDefinition module) + { + try + { + // collect environment details + _realmEnvMetrics[UserEnvironment.UserId] = GetAnonymizedUserId(); + _realmEnvMetrics[UserEnvironment.LegacyUserId] = GetLegacyAnonymizedUserId(); + _realmEnvMetrics[UserEnvironment.ProjectId] = SHA256Hash(Encoding.UTF8.GetBytes(_config.ProjectId ?? module.Assembly.Name.Name)); + _realmEnvMetrics[UserEnvironment.RealmSdk] = "dotnet"; + _realmEnvMetrics[UserEnvironment.RealmSdkVersion] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + _realmEnvMetrics[UserEnvironment.Language] = "c#"; + _realmEnvMetrics[UserEnvironment.LanguageVersion] = InferLanguageVersion(_config.NetFrameworkTarget, _config.NetFrameworkTargetVersion); + _realmEnvMetrics[UserEnvironment.HostOsType] = GetHostOsName(); + _realmEnvMetrics[UserEnvironment.HostOsVersion] = Environment.OSVersion.Version.ToString(); + _realmEnvMetrics[UserEnvironment.HostCpuArch] = GetHostCpuArchitecture(); + _realmEnvMetrics[UserEnvironment.TargetOsType] = _config.TargetOSName; + _realmEnvMetrics[UserEnvironment.TargetCpuArch] = _config.TargetArchitecture; + _realmEnvMetrics[UserEnvironment.TargetOsVersion] = _config.TargetOsVersion; + _realmEnvMetrics[UserEnvironment.TargetOsMinimumVersion] = _config.TargetOsMinimumVersion; + _realmEnvMetrics[UserEnvironment.CoreVersion] = CoreVersion; + _realmEnvMetrics[UserEnvironment.FrameworkUsedInConjunction] = _config.FrameworkName; + _realmEnvMetrics[UserEnvironment.FrameworkUsedInConjunctionVersion] = _config.FrameworkVersion; + _realmEnvMetrics[UserEnvironment.SdkInstallationMethod] = _config.InstallationMethod; + _realmEnvMetrics[UserEnvironment.NetFramework] = _config.NetFrameworkTarget; + _realmEnvMetrics[UserEnvironment.NetFrameworkVersion] = _config.NetFrameworkTargetVersion; + _realmEnvMetrics[UserEnvironment.Compiler] = _config.Compiler; + + foreach (var type in module.Types.ToArray()) + { + InternalAnalyzeSdkApi(type); + } + + // We need to first analyze the features before we can set `IsSyncEnabled`. + _realmFeaturesToAnalyze.TryGetValue(Feature.PartitionSyncConfiguration, out var isPbsUsed); + _realmFeaturesToAnalyze.TryGetValue(Feature.FlexibleSyncConfiguration, out var isFlxSUsed); + _realmEnvMetrics[UserEnvironment.IsSyncEnabled] = (isPbsUsed == 1 || isFlxSUsed == 1).ToString().ToLower(); + } + catch (Exception e) + { + _logger.Error($"Could not analyze the user's assembly.{Environment.NewLine}{e.Message}"); + } + } + + public void AnalyzeRealmClassProperties(WeaveTypeResult[] types) + { + if (_config.AnalyticsCollection == AnalyticsCollection.Disabled) + { + return; + } + + _analyzeUserAssemblyTask.Wait(); + + foreach (var type in types) + { + if (type.Properties == null) + { + continue; + } + + foreach (var propertyResult in type.Properties.Where(p => p.Woven)) + { + var property = propertyResult.Property; + + var key = property!.PropertyType.Name; + if (!_classAnalysisSetters.ContainsKey(key) && property.PropertyType.MetadataType == MetadataType.Class) + { + key = "Class"; + } + + AnalyzeClassFeature(key, property); + + foreach (var attribute in property.CustomAttributes) + { + AnalyzeClassFeature(attribute.AttributeType.Name, property); + } + } + } + + void AnalyzeClassFeature(string key, PropertyDefinition property) + { + if (_classAnalysisSetters.TryGetValue(key, out var featureFunc)) + { + var analysisResult = featureFunc(property); + + if (!string.IsNullOrEmpty(analysisResult.DictKey)) + { + _realmFeaturesToAnalyze[analysisResult.DictKey] = 1; + } + + if (analysisResult.ShouldDelete) + { + _classAnalysisSetters.Remove(key); + } + } + } + } + + private void InternalAnalyzeSdkApi(TypeDefinition type) + { + if (!type.IsClass) + { + return; + } + + if (type.IsIAsymmetricObjectImplementor(_references) || type.IsAsymmetricObjectDescendant(_references)) + { + _realmFeaturesToAnalyze[Feature.IAsymmetricObject] = 1; + } + else if (type.IsIEmbeddedObjectImplementor(_references) || type.IsEmbeddedObjectDescendant(_references)) + { + _realmFeaturesToAnalyze[Feature.IEmbeddedObject] = 1; + } + + AnalyzeClassMethods(type); + + foreach (var innerType in type.NestedTypes) + { + InternalAnalyzeSdkApi(innerType); + } + } + + private void AnalyzeClassMethods(TypeDefinition type) + { + var prefixes = new[] { "get_", "set_", "add_" }; + + foreach (var method in type.Methods) + { + if (!method.HasBody) + { + continue; + } + + foreach (var cil in method.Body.Instructions) + { + var key = (cil.Operand as MemberReference)?.Name; + if (key.IsNullOrEmpty()) + { + continue; + } + + var (prefix, index) = prefixes.Select(p => (p, key.IndexOf(p, StringComparison.Ordinal))) + .OrderByDescending(p => p.Item2) + .First(); + + if (index > -1) + { + // when dealing with: + // set_ShouldCompactOnLaunch + // add_RealmChanged + // add_PropertyChanged + // get_DynamicApi + key = key[prefix.Length..]; + } + + if (!_apiAnalysisSetters.ContainsKey(key) && + cil.Operand is MethodReference methodReference && + methodReference.ReturnType.DeclaringType != null) + { + // when dealing with ThreadSafeReference + key = methodReference.ReturnType.DeclaringType.Name; + } + + if (!_apiAnalysisSetters.ContainsKey(key) && key == ".ctor") + { + key = ((MemberReference)cil.Operand).DeclaringType.Name; + } + + if (!_apiAnalysisSetters.TryGetValue(key, out var featureFunc)) + { + continue; + } + + var analysisResult = featureFunc.Invoke(cil); + + if (!string.IsNullOrEmpty(analysisResult.DictKey)) + { + _realmFeaturesToAnalyze[analysisResult.DictKey] = 1; + } + + if (analysisResult.ShouldDelete) + { + _apiAnalysisSetters.Remove(key); + } + } + } + } + + public async Task SubmitAnalytics() + { + var payload = "Analytics disabled"; + + if (_config.AnalyticsCollection != AnalyticsCollection.Disabled) + { + // this is necessary since when not in the assembly that has the models + // AnalyzeRealmClassProperties won't be called + _analyzeUserAssemblyTask.Wait(); + + try + { + const string sendAddr = "https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/v2/metric?data="; + payload = GetJsonPayload(); + + if (_config.AnalyticsCollection != AnalyticsCollection.DryRun) + { + var base64Payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload)); + await SendRequest(sendAddr, base64Payload, string.Empty); + } + } + catch (Exception e) + { + payload = e.Message; + } + } + + if (!_config.AnalyticsLogPath.IsNullOrEmpty()) + { + File.WriteAllText(_config.AnalyticsLogPath, payload); + } + } + + private string GetJsonPayload() + { + var jsonPayload = new StringBuilder(); + + jsonPayload.Append('{'); + + AppendKeyValues(_realmEnvMetrics); + jsonPayload.Append(','); + AppendKeyValues(_realmFeaturesToAnalyze, Metric.SdkFeatures); + jsonPayload.Append('}'); + + return jsonPayload.ToString(); + + void AppendKeyValues(IDictionary dict, IDictionary? keyMapping = null) + { + var mapping = dict + .Select(kvp => + { + if (kvp.Value is byte and 0 || + (kvp.Value is string s && string.IsNullOrEmpty(s))) + { + // skip empty strings/0 + return null; + } + + var key = keyMapping == null ? kvp.Key : keyMapping[kvp.Key]; + var value = kvp.Value is string ? $"\"{kvp.Value}\"" : $"{kvp.Value}"; + return $"\"{key}\":{value}"; + }) + .Where(s => s != null); + + jsonPayload.Append(string.Join(",", mapping)); + } + } + + private static async Task SendRequest(string prefixAddr, string payload, string suffixAddr) + { + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromSeconds(4); + await httpClient.GetAsync(new Uri(prefixAddr + payload + suffixAddr)); + } + + private static bool IsInRealmNamespace(object operand) + { + if (operand is not MemberReference memberReference) + { + return false; + } + + return memberReference.DeclaringType.FullName.StartsWith("Realms", StringComparison.Ordinal); + } + + public class Config + { + public AnalyticsCollection AnalyticsCollection { get; init; } + + public string? AnalyticsLogPath { get; init; } + + required public string TargetOSName { get; init; } + + required public string NetFrameworkTarget { get; init; } + + required public string NetFrameworkTargetVersion { get; init; } + + required public string InstallationMethod { get; init; } + + required public string FrameworkName { get; init; } + + required public string FrameworkVersion { get; init; } + + required public string Compiler { get; init; } + + // These are only available on Unity for now. + public string TargetArchitecture { get; init; } = Metric.Unknown(); + + public string TargetOsVersion { get; init; } = Metric.Unknown(); + + public string TargetOsMinimumVersion { get; init; } = Metric.Unknown(); + + public string? ProjectId { get; init; } + } + + public enum AnalyticsCollection + { + Disabled, + DryRun, + Full, + } + + private readonly struct FeatureAnalysisResult + { + public bool ShouldDelete { get; } + + public string DictKey { get; } + + public FeatureAnalysisResult(bool isToDelete = false, string dictKey = "") + { + ShouldDelete = isToDelete; + DictKey = dictKey; + } + } + } +} diff --git a/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs b/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs new file mode 100644 index 0000000000..54be15b482 --- /dev/null +++ b/Realm/Realm.Weaver/Analytics/AnalyticsUtils.cs @@ -0,0 +1,304 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using Mono.Cecil; +using static RealmWeaver.Metric; +using OperatingSystem = RealmWeaver.Metric.OperatingSystem; + +namespace RealmWeaver +{ + internal static class AnalyticsUtils + { + public static string GetTargetOsName(FrameworkName frameworkName) + { + var targetOs = frameworkName.Identifier; + + if (targetOs.ContainsIgnoreCase("android")) + { + return OperatingSystem.Android; + } + + if (targetOs.ContainsIgnoreCase("ios")) + { + return OperatingSystem.Ios; + } + + if (targetOs.ContainsIgnoreCase("mac")) + { + return OperatingSystem.MacOS; + } + + if (targetOs.ContainsIgnoreCase("tvos")) + { + return OperatingSystem.TvOs; + } + + if (targetOs.ContainsIgnoreCase("linux")) + { + return OperatingSystem.Linux; + } + + if (targetOs.ContainsIgnoreCase("win") || targetOs.ContainsIgnoreCase("net4")) + { + return OperatingSystem.Windows; + } + + if (targetOs.ContainsIgnoreCase("core") || + targetOs.ContainsIgnoreCase("standard") || + targetOs.ContainsIgnoreCase("net")) + { + return OperatingSystem.CrossPlatform; + } + + return Unknown(frameworkName.Identifier); + } + + public static FrameworkInfo GetFrameworkAndVersion(ModuleDefinition module) + { + // the order in the array matters as we first need to look at the libraries (maui and forms) + // and then at the frameworks (xamarin native, Catalyst and UWP) + var possibleFrameworks = new Dictionary + { + { "Microsoft.Maui", Framework.Maui }, + { "Xamarin.Forms.Core", Framework.XamarinForms }, + { "Xamarin.iOS", Framework.Xamarin }, + { "Xamarin.tvOS", Framework.Xamarin }, + { "Xamarin.Mac", Framework.Xamarin }, + { "Mono.Android", Framework.Xamarin }, + { "Microsoft.MacCatalyst", Framework.MacCatalyst }, + { "Windows.Foundation.UniversalApiContract", Framework.Uwp }, + }; + + foreach (var kvp in possibleFrameworks) + { + var frameworkUsedInConjunction = module.AssemblyReferences.SingleOrDefault(a => a.Name == kvp.Key); + if (frameworkUsedInConjunction != null) + { + return new(kvp.Value, frameworkUsedInConjunction.Version.ToString()); + } + } + + return new("No framework of interest", "0.0.0"); + } + + public static string SHA256Hash(byte[] bytes, bool useLegacyEncoding = false) + { + using var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(bytes); + return useLegacyEncoding ? BitConverter.ToString(hash) : Convert.ToBase64String(hash); + } + + public static string GetHostCpuArchitecture() => RuntimeInformation.OSArchitecture switch + { + Architecture.X86 => CpuArchitecture.X86, + Architecture.Arm => CpuArchitecture.Arm, + Architecture.Arm64 => CpuArchitecture.Arm64, + Architecture.X64 => CpuArchitecture.X64, + _ => Unknown(RuntimeInformation.OSArchitecture.ToString()) + }; + + public static string GetHostOsName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystem.Windows; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystem.Linux; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystem.MacOS; + } + + return Unknown(System.Environment.OSVersion.Platform.ToString()); + } + + public static string InferLanguageVersion(string netFramework, string netFrameworkVersion) + { + // We don't have a reliable way to get the version in the weaver so we're using the default version + // associated with the framework. + // Values taken from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version + if (!netFramework.ContainsIgnoreCase("net")) + { + // Likely this isn't the model assembly, but the platform specific one + return Unknown(netFramework); + } + + if (netFrameworkVersion.ContainsIgnoreCase("2.0") || + netFrameworkVersion.ContainsIgnoreCase("4.")) + { + return "7.3"; + } + + if (netFrameworkVersion.ContainsIgnoreCase("2.1") || + netFrameworkVersion.ContainsIgnoreCase("3.1")) + { + return "8"; + } + + if (netFrameworkVersion.ContainsIgnoreCase("5.0")) + { + return "9"; + } + + if (netFrameworkVersion.ContainsIgnoreCase("6.0")) + { + return "10"; + } + + if (netFrameworkVersion.ContainsIgnoreCase("7.0")) + { + return "11"; + } + + return Unknown(); + } + + // Knowledge on unique machine Ids for different OSes obtained from https://github.com/denisbrodbeck/machineid + public static string GetAnonymizedUserId() + { + var id = string.Empty; + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var machineIdToParse = RunProcess("reg", "QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography -v MachineGuid"); + var regex = new Regex("\\s+MachineGuid\\s+\\w+\\s+((\\w+-?)+)", RegexOptions.Multiline); + var match = regex.Match(machineIdToParse); + + if (match.Groups.Count > 1) + { + id = match.Groups[1].Value; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var machineIdToParse = RunProcess("ioreg", "-rd1 -c IOPlatformExpertDevice"); + var regex = new Regex(".*\\\"IOPlatformUUID\\\"\\s=\\s\\\"(.+)\\\"", RegexOptions.Multiline); + var match = regex.Match(machineIdToParse); + + if (match.Groups.Count > 1) + { + id = match.Groups[1].Value; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + id = File.ReadAllText("/etc/machine-id"); + } + + if (id.Length == 0) + { + return Unknown(); + } + + // We're salting the id with an hardcoded byte array just to avoid that a machine is recognizable across + // unrelated projects that use the same mechanics to obtain a machine's ID + const string salt = "Realm is great"; + var saltedId = Encoding.UTF8.GetBytes(id + salt); + return SHA256Hash(saltedId); + } + catch + { + return Unknown(); + } + } + + public static string GetLegacyAnonymizedUserId() + { + try + { + var id = NetworkInterface.GetAllNetworkInterfaces() + .Where(n => n.Name == "en0" || (n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback)) + .Select(n => n.GetPhysicalAddress().GetAddressBytes()) + .First(); + return SHA256Hash(id, useLegacyEncoding: true); + } + catch + { + return Unknown(); + } + } + + private static bool ContainsIgnoreCase(this string @this, string strCompare) => + @this.IndexOf(strCompare, StringComparison.OrdinalIgnoreCase) > -1; + + private static string RunProcess(string filename, string arguments) + { + using var proc = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = filename, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, +#if DEBUG + RedirectStandardError = true, +#endif + } + }; + + proc.Start(); + + var stdout = new StringBuilder(); + while (!proc.HasExited) + { + stdout.AppendLine(proc.StandardOutput.ReadToEnd()); +#if DEBUG + stdout.AppendLine(proc.StandardError.ReadToEnd()); +#endif + } + + stdout.AppendLine(proc.StandardOutput.ReadToEnd()); +#if DEBUG + stdout.AppendLine(proc.StandardError.ReadToEnd()); +#endif + + return stdout.ToString(); + } + + public readonly struct FrameworkInfo + { + public string Name { get; } + + public string Version { get; } + + public FrameworkInfo(string name, string version) + { + Name = name; + Version = version; + } + } + } +} diff --git a/Realm/Realm.Weaver/Analytics/Metric.cs b/Realm/Realm.Weaver/Analytics/Metric.cs new file mode 100644 index 0000000000..2b372cc698 --- /dev/null +++ b/Realm/Realm.Weaver/Analytics/Metric.cs @@ -0,0 +1,210 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace RealmWeaver +{ + internal static class Metric + { + public static string Unknown(string? clarifier = null) + { + var result = "Unknown"; + if (!clarifier.IsNullOrEmpty()) + { + result += $" ({clarifier})"; + } + + return result; + } + + public static class OperatingSystem + { + public const string Linux = "Linux"; + public const string MacOS = "macOS"; + public const string Windows = "Windows"; + public const string CrossPlatform = "Cross Platform"; + public const string Android = "Android"; + public const string Ios = "iOS"; + public const string IpadOs = "iPadOS"; + public const string WatchOs = "watchOS"; + public const string TvOs = "tvOS"; + public const string XboxOne = "XboxOne"; + } + + public static class CpuArchitecture + { + public const string X86 = "x86"; + public const string X64 = "x64"; + public const string Arm = "Arm"; + public const string Arm64 = "Arm64"; + public const string Universal = "Universal"; + } + + public static class Framework + { + public const string Unity = "Unity"; + public const string UnityEditor = "Unity Editor"; + public const string Maui = "MAUI"; + public const string Xamarin = "Xamarin"; + public const string XamarinForms = "Xamarin Forms"; + public const string Uwp = "UWP"; + public const string MacCatalyst = "MacCatalyst"; + } + + public static class Environment + { + public const string LegacyUserId = "distinct_id"; + public const string UserId = "builder_id"; + public const string ProjectId = "Anonymized Bundle ID"; + public const string RealmSdk = "Binding"; + public const string RealmSdkVersion = "Realm Version"; + public const string Language = "Language"; + public const string LanguageVersion = "Language Version"; + public const string HostOsType = "Host OS Type"; + public const string HostOsVersion = "Host OS Version"; + public const string HostCpuArch = "Host CPU Arch"; + public const string TargetOsType = "Target OS Type"; + public const string TargetCpuArch = "Target CPU Arch"; + public const string TargetOsMinimumVersion = "Target OS Minimum Version"; + public const string TargetOsVersion = "Target OS Version"; + public const string CoreVersion = "Core Version"; + public const string IsSyncEnabled = "Sync Enabled"; + public const string FrameworkUsedInConjunction = "Framework"; // this refers to UI frameworks and similar Realm is used together with + public const string FrameworkUsedInConjunctionVersion = "Framework Version"; + public const string SdkInstallationMethod = "Installation Method"; + public const string NetFramework = "Net Framework"; + public const string NetFrameworkVersion = "Net Framework Version"; + public const string Compiler = "Compiler"; + + // These are not currently supported + [SuppressMessage("Performance", "CA1823:Avoid unused private fields", Justification = "Placeholder")] + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Placeholder")] + private const string _IdeUsed = "IDE"; + + [SuppressMessage("Performance", "CA1823:Avoid unused private fields", Justification = "Placeholder")] + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Placeholder")] + private const string _IdeUsedVersion = "IDE Version"; + } + + public static class Feature + { + // ReSharper disable InconsistentNaming + public const string IEmbeddedObject = nameof(IEmbeddedObject); + public const string IAsymmetricObject = nameof(IAsymmetricObject); + public const string ReferenceList = nameof(ReferenceList); + public const string PrimitiveList = nameof(PrimitiveList); + public const string ReferenceDictionary = nameof(ReferenceDictionary); + public const string PrimitiveDictionary = nameof(PrimitiveDictionary); + public const string ReferenceSet = nameof(ReferenceSet); + public const string PrimitiveSet = nameof(PrimitiveSet); + public const string RealmInteger = nameof(RealmInteger); + public const string RealmObjectReference = nameof(RealmObjectReference); + public const string RealmValue = nameof(RealmValue); + public const string BacklinkAttribute = nameof(BacklinkAttribute); + public const string GetInstanceAsync = nameof(GetInstanceAsync); + public const string GetInstance = nameof(GetInstance); + public const string Find = nameof(Find); + public const string WriteAsync = nameof(WriteAsync); + public const string ThreadSafeReference = nameof(ThreadSafeReference); + public const string Add = nameof(Add); + public const string ShouldCompactOnLaunch = nameof(ShouldCompactOnLaunch); + public const string MigrationCallback = nameof(MigrationCallback); + public const string RealmChanged = nameof(RealmChanged); + public const string ListSubscribeForNotifications = nameof(ListSubscribeForNotifications); + public const string SetSubscribeForNotifications = nameof(SetSubscribeForNotifications); + public const string DictionarySubscribeForNotifications = nameof(DictionarySubscribeForNotifications); + public const string ResultSubscribeForNotifications = nameof(ResultSubscribeForNotifications); + public const string RecoverOrDiscardUnsyncedChangesHandler = nameof(RecoverOrDiscardUnsyncedChangesHandler); + public const string RecoverUnsyncedChangesHandler = nameof(RecoverUnsyncedChangesHandler); + public const string DiscardUnsyncedChangesHandler = nameof(DiscardUnsyncedChangesHandler); + public const string ManualRecoveryHandler = nameof(ManualRecoveryHandler); + public const string GetProgressObservable = nameof(GetProgressObservable); + public const string PartitionSyncConfiguration = nameof(PartitionSyncConfiguration); + public const string FlexibleSyncConfiguration = nameof(FlexibleSyncConfiguration); + public const string Anonymous = nameof(Anonymous); + public const string EmailPassword = nameof(EmailPassword); + public const string Facebook = nameof(Facebook); + public const string Google = nameof(Google); + public const string Apple = nameof(Apple); + public const string JWT = nameof(JWT); + public const string ApiKey = nameof(ApiKey); + public const string Function = nameof(Function); + public const string CallAsync = nameof(CallAsync); + public const string GetMongoClient = nameof(GetMongoClient); + public const string DynamicApi = nameof(DynamicApi); + public const string ConnectionNotification = nameof(ConnectionNotification); + public const string ObjectNotification = nameof(ObjectNotification); + } + + // This holds a mapping from Feature -> the name we send to DW. + public static readonly Dictionary SdkFeatures = new() + { + [Feature.IEmbeddedObject] = "Embedded_Object", + [Feature.IAsymmetricObject] = "Asymmetric_Object", + [Feature.ReferenceList] = "Object_List", + [Feature.PrimitiveList] = "Primitive_List", + [Feature.ReferenceDictionary] = "Object_Dict", + [Feature.PrimitiveDictionary] = "Primitive_Dict", + [Feature.ReferenceSet] = "Object_Set", + [Feature.PrimitiveSet] = "Primitive_Set", + [Feature.RealmInteger] = "Counter", + [Feature.RealmObjectReference] = "Object_Link", + [Feature.RealmValue] = "Mixed", + [Feature.BacklinkAttribute] = "Backlink", + + [Feature.GetInstanceAsync] = "Async_Open", + [Feature.GetInstance] = "Sync_Open", + + [Feature.Find] = "Find_PK", + [Feature.WriteAsync] = "Write_Async", + [Feature.ThreadSafeReference] = "Thread_Safe_Reference", + [Feature.Add] = "Insert_Modified", + [Feature.ShouldCompactOnLaunch] = "Compact_On_Launch", + [Feature.MigrationCallback] = "Migration_Block", + [Feature.RealmChanged] = "Realm_Notifications", + [Feature.ListSubscribeForNotifications] = "List_Notifications", + [Feature.SetSubscribeForNotifications] = "Set_Notifications", + [Feature.DictionarySubscribeForNotifications] = "Dict_Notifications", + [Feature.ResultSubscribeForNotifications] = "Results_Notifications", + [Feature.ObjectNotification] = "Object_Notifications", + [Feature.RecoverOrDiscardUnsyncedChangesHandler] = "CR_Recover_Discard", + [Feature.RecoverUnsyncedChangesHandler] = "CR_Recover", + [Feature.DiscardUnsyncedChangesHandler] = "CR_Discard", + [Feature.ManualRecoveryHandler] = "CR_Manual", + [Feature.GetProgressObservable] = "Progress_Notification", + [Feature.PartitionSyncConfiguration] = "Pbs_Sync", + [Feature.FlexibleSyncConfiguration] = "Flx_Sync", + [Feature.Anonymous] = "Auth_Anon", + [Feature.EmailPassword] = "Auth_Email", + [Feature.Facebook] = "Auth_Facebook", + [Feature.Google] = "Auth_Google", + [Feature.Apple] = "Auth_Apple", + [Feature.JWT] = "Auth_JWT", + [Feature.ApiKey] = "Auth_API_Key", + [Feature.Function] = "Auth_Function", + [Feature.CallAsync] = "Remote_Function", + [Feature.GetMongoClient] = "Remote_Mongo", + [Feature.DynamicApi] = "Dynamic_API", + [Feature.ConnectionNotification] = "Connection_Notification", + + // ["NOT_SUPPORTED_YET"] = "Query_Async", + }; + } +} diff --git a/Realm/Realm.Weaver/Extensions/MethodReferenceExtensions.cs b/Realm/Realm.Weaver/Extensions/MethodReferenceExtensions.cs index 19d306b037..718cec4dbe 100644 --- a/Realm/Realm.Weaver/Extensions/MethodReferenceExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/MethodReferenceExtensions.cs @@ -22,34 +22,6 @@ [EditorBrowsable(EditorBrowsableState.Never)] internal static class MethodReferenceExtensions { - public static MethodReference MakeHostInstanceGeneric(this MethodReference @this, params TypeReference[] genericArguments) - { - var genericDeclaringType = new GenericInstanceType(@this.DeclaringType); - foreach (var genericArgument in genericArguments) - { - genericDeclaringType.GenericArguments.Add(genericArgument); - } - - var reference = new MethodReference(@this.Name, @this.ReturnType, genericDeclaringType) - { - HasThis = @this.HasThis, - ExplicitThis = @this.ExplicitThis, - CallingConvention = @this.CallingConvention - }; - - foreach (var parameter in @this.Parameters) - { - reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); - } - - foreach (var genericParam in @this.GenericParameters) - { - reference.GenericParameters.Add(new GenericParameter(genericParam.Name, reference)); - } - - return reference; - } - public static bool ConstructsType(this MethodReference @this, TypeReference type) { return @this.DeclaringType.IsSameAs(type) && @this.Name == ".ctor"; diff --git a/Realm/Realm.Weaver/Extensions/ModuleDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/ModuleDefinitionExtensions.cs index bba4a144ce..807ea7824a 100644 --- a/Realm/Realm.Weaver/Extensions/ModuleDefinitionExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/ModuleDefinitionExtensions.cs @@ -23,18 +23,13 @@ [EditorBrowsable(EditorBrowsableState.Never)] internal static class ModuleDefinitionExtensions { - public static ModuleDefinition ResolveReference(this ModuleDefinition module, string assembly) + public static ModuleDefinition? ResolveReference(this ModuleDefinition module, string assembly) { var assemblyNameReference = module.FindReference(assembly); - if (assemblyNameReference != null) - { - return module.AssemblyResolver.Resolve(assemblyNameReference).MainModule; - } - - return null; + return assemblyNameReference != null ? module.AssemblyResolver.Resolve(assemblyNameReference).MainModule : null; } - public static AssemblyNameReference FindReference(this ModuleDefinition module, string assembly) + public static AssemblyNameReference? FindReference(this ModuleDefinition module, string assembly) { return module.AssemblyReferences.SingleOrDefault(a => a.Name == assembly); } diff --git a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs index 49d65e9321..fcdcd23d9b 100644 --- a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs @@ -21,7 +21,6 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; using RealmWeaver; @@ -30,8 +29,6 @@ [EditorBrowsable(EditorBrowsableState.Never)] internal static class PropertyDefinitionExtensions { - private static readonly Regex NullableRegex = new Regex("^System.Nullable`1<(?.*)>$"); - private static readonly IEnumerable _indexableTypes = new[] { StringTypeName, @@ -51,17 +48,17 @@ internal static bool IsAutomatic(this PropertyDefinition property) return property.GetMethod.CustomAttributes.Any(attr => attr.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName); } - internal static bool IsIList(this PropertyDefinition property) + private static bool IsIList(this PropertyDefinition property) { return property.IsType("IList`1", "System.Collections.Generic"); } - internal static bool IsISet(this PropertyDefinition property) + private static bool IsISet(this PropertyDefinition property) { return property.IsType("ISet`1", "System.Collections.Generic"); } - internal static bool IsIDictionary(this PropertyDefinition property) + private static bool IsIDictionary(this PropertyDefinition property) { return property.IsType("IDictionary`2", "System.Collections.Generic"); } @@ -115,11 +112,6 @@ internal static bool IsNullable(this PropertyDefinition property) return property.PropertyType.IsNullable(); } - internal static bool IsNullable(this TypeReference reference) - { - return NullableRegex.IsMatch(reference.FullName); - } - internal static bool IsSingle(this PropertyDefinition property) { return property.PropertyType.FullName == SingleTypeName; @@ -165,7 +157,7 @@ internal static bool IsDescendantOf(this PropertyDefinition property, TypeRefere return property.PropertyType.Resolve().BaseType.IsSameAs(other); } - internal static FieldReference GetBackingField(this PropertyDefinition property) + internal static FieldReference? GetBackingField(this PropertyDefinition property) { return property.GetMethod.Body.Instructions .Where(o => o.OpCode == OpCodes.Ldfld) @@ -203,20 +195,6 @@ internal static bool IsIndexable(this PropertyDefinition property, ImportedRefer return _indexableTypes.Contains(propertyType.FullName); } - public static bool IsEmbeddedObjectInheritor(this TypeDefinition type, ImportedReferences references) => - type.BaseType.IsSameAs(references.EmbeddedObject); - - public static bool IsRealmObjectInheritor(this TypeDefinition type, ImportedReferences references) => - type.BaseType.IsSameAs(references.RealmObject); - - public static bool IsAsymmetricObjectInheritor(this TypeDefinition type, ImportedReferences references) => - type.BaseType.IsSameAs(references.AsymmetricObject); - - public static bool IsValidRealmObjectBaseInheritor(this TypeDefinition type, ImportedReferences references) => - type.IsEmbeddedObjectInheritor(references) || - type.IsRealmObjectInheritor(references) || - type.IsAsymmetricObjectInheritor(references); - public static bool ContainsRealmObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsRealmObjectInheritor(references); @@ -226,32 +204,10 @@ public static bool ContainsAsymmetricObject(this PropertyDefinition property, Im public static bool ContainsEmbeddedObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsEmbeddedObjectInheritor(references); - public static bool IsRealmInteger(this TypeReference type, out bool isNullable, out TypeReference genericArgumentType) - { - var nullableMatch = NullableRegex.Match(type.FullName); - isNullable = nullableMatch.Success; - if (isNullable) - { - var genericType = (GenericInstanceType)type; - type = genericType.GenericArguments.Single(); - } - - var result = type.Name == "RealmInteger`1" && type.Namespace == "Realms"; - if (result) - { - var genericType = (GenericInstanceType)type; - genericArgumentType = genericType.GenericArguments.Single(); - return true; - } - - genericArgumentType = null; - return false; - } - private static bool IsType(this PropertyDefinition property, string name, string @namespace) { return property.PropertyType.Name == name && property.PropertyType.Namespace == @namespace; } - public static SequencePoint GetSequencePoint(this PropertyDefinition property) => property.GetMethod?.DebugInformation?.SequencePoints.FirstOrDefault() ?? property.SetMethod?.DebugInformation?.SequencePoints.FirstOrDefault(); + public static SequencePoint? GetSequencePoint(this PropertyDefinition property) => property.GetMethod?.DebugInformation?.SequencePoints.FirstOrDefault() ?? property.SetMethod?.DebugInformation?.SequencePoints.FirstOrDefault(); } diff --git a/Realm/Realm.Weaver/Extensions/StringExtensions.cs b/Realm/Realm.Weaver/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..dbc68fbfe3 --- /dev/null +++ b/Realm/Realm.Weaver/Extensions/StringExtensions.cs @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2023 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Diagnostics.CodeAnalysis; + +internal static class StringExtensions +{ + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) => string.IsNullOrEmpty(value); +} diff --git a/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs new file mode 100644 index 0000000000..4f9e44be47 --- /dev/null +++ b/Realm/Realm.Weaver/Extensions/TypeDefinitionExtensions.cs @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.ComponentModel; +using Mono.Cecil; +using RealmWeaver; + +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class TypeDefinitionExtensions +{ + public static bool IsEmbeddedObjectInheritor(this TypeDefinition type, ImportedReferences references) => + type.BaseType.IsSameAs(references.EmbeddedObject); + + public static bool IsRealmObjectInheritor(this TypeDefinition type, ImportedReferences references) => + type.BaseType.IsSameAs(references.RealmObject); + + public static bool IsAsymmetricObjectInheritor(this TypeDefinition type, ImportedReferences references) => + type.BaseType.IsSameAs(references.AsymmetricObject); + + public static bool IsValidRealmObjectBaseInheritor(this TypeDefinition type, ImportedReferences references) => + type.IsRealmObjectInheritor(references) || + type.IsEmbeddedObjectInheritor(references) || + type.IsAsymmetricObjectInheritor(references); + + public static bool IsIEmbeddedObjectImplementor(this TypeDefinition type, ImportedReferences references) => + IsImplementorOf(type, references.IEmbeddedObject); + + public static bool IsIAsymmetricObjectImplementor(this TypeDefinition type, ImportedReferences references) => + IsImplementorOf(type, references.IAsymmetricObject); + + public static bool IsImplementorOf(TypeDefinition @this, params TypeReference[] targetInterfaces) + { + foreach (var @interface in @this.Interfaces) + { + foreach (var targetInterface in targetInterfaces) + { + if (@interface.InterfaceType.IsSameAs(targetInterface)) + { + return true; + } + } + } + + return false; + } +} diff --git a/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs b/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs index 81fafe5d0b..7969d41949 100644 --- a/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/TypeReferenceExtensions.cs @@ -17,7 +17,9 @@ //////////////////////////////////////////////////////////////////////////// using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; @@ -26,11 +28,13 @@ [EditorBrowsable(EditorBrowsableState.Never)] internal static class TypeReferenceExtensions { - public static SequencePoint GetSequencePoint(this TypeDefinition @this) + private static readonly Regex NullableRegex = new("^System.Nullable`1<(?.*)>$"); + + public static SequencePoint? GetSequencePoint(this TypeDefinition @this) { return GetCtorSequencePoint() ?? GetPropSequencePoint(); - SequencePoint GetCtorSequencePoint() + SequencePoint? GetCtorSequencePoint() { return @this.GetConstructors() .OrderBy(c => c.Parameters.Count) @@ -38,7 +42,7 @@ SequencePoint GetCtorSequencePoint() .FirstOrDefault(); } - SequencePoint GetPropSequencePoint() + SequencePoint? GetPropSequencePoint() { return @this.Properties .Select(p => p.GetSequencePoint()) @@ -46,7 +50,78 @@ SequencePoint GetPropSequencePoint() } } - private static bool IsDescendantOf(TypeReference @this, params TypeReference[] targetTypes) + public static bool IsAnyRealmObject(this TypeReference @this, ImportedReferences references) + => @this.IsIRealmObjectBaseImplementor(references) + || @this.IsRealmObjectDescendant(references); + + public static bool IsRealmObjectDescendant(this TypeReference @this, ImportedReferences references) => + IsDescendantOf(@this, references.RealmObject, references.EmbeddedObject, references.AsymmetricObject); + + public static bool IsAsymmetricObjectDescendant(this TypeReference @this, ImportedReferences references) => + IsDescendantOf(@this, references.AsymmetricObject); + + public static bool IsEmbeddedObjectDescendant(this TypeReference @this, ImportedReferences references) => + IsDescendantOf(@this, references.EmbeddedObject); + + public static bool IsSameAs(this TypeReference? @this, TypeReference? other) + { + if (@this is null || other is null) + { + return false; + } + + return @this.FullName == other.FullName && @this.GetAssemblyName() == other.GetAssemblyName(); + } + + private static string GetAssemblyName(this TypeReference @this) + { + return @this.Scope.MetadataScopeType switch + { + MetadataScopeType.AssemblyNameReference => ((AssemblyNameReference)@this.Scope).FullName, + MetadataScopeType.ModuleReference => ((ModuleReference)@this.Scope).Name, + _ => ((ModuleDefinition)@this.Scope).Assembly.FullName + }; + } + + public static string ToFriendlyString(this TypeReference type) + { + if (!string.IsNullOrEmpty(type.Namespace)) + { + return type.ToString().Replace(type.Namespace, string.Empty).TrimStart('.'); + } + + return type.ToString(); + } + + public static bool IsRealmInteger(this TypeReference type, out bool isNullable, [NotNullWhen(true)] out TypeReference? genericArgumentType) + { + var nullableMatch = NullableRegex.Match(type.FullName); + isNullable = nullableMatch.Success; + if (isNullable) + { + var genericType = (GenericInstanceType)type; + type = genericType.GenericArguments.Single(); + } + + var result = type.Name == "RealmInteger`1" && type.Namespace == "Realms"; + if (result) + { + var genericType = (GenericInstanceType)type; + genericArgumentType = genericType.GenericArguments.Single(); + return true; + } + + genericArgumentType = null; + return false; + } + + public static bool IsNullable(this TypeReference reference) => + NullableRegex.IsMatch(reference.FullName); + + private static bool IsIRealmObjectBaseImplementor(this TypeReference type, ImportedReferences references) => + IsImplementorOf(type, references.IRealmObjectBase); + + private static bool IsDescendantOf(TypeReference? @this, params TypeReference[] targetTypes) { try { @@ -57,7 +132,7 @@ private static bool IsDescendantOf(TypeReference @this, params TypeReference[] t return false; } - var definition = @this?.Resolve(); + var definition = @this.Resolve(); if (definition == null) { return false; @@ -83,46 +158,24 @@ private static bool IsDescendantOf(TypeReference @this, params TypeReference[] t return false; } - public static bool IsRealmObjectDescendant(this TypeReference @this, ImportedReferences references) - { - return IsDescendantOf(@this, references.RealmObject, references.EmbeddedObject, references.AsymmetricObject); - } - - public static bool IsAsymmetricObjectDescendant(this TypeReference @this, ImportedReferences references) + private static bool IsImplementorOf(TypeReference? @this, params TypeReference[] targetInterfaces) { - return IsDescendantOf(@this, references.AsymmetricObject); - } - - public static bool IsSameAs(this TypeReference @this, TypeReference other) - { - if (@this is null || other is null) + try { - return false; - } - - return @this.FullName == other.FullName && @this.GetAssemblyName() == other.GetAssemblyName(); - } + if (@this == null || !Weaver.ShouldTraverseAssembly(@this.Module.Assembly.Name)) + { + return false; + } - public static string GetAssemblyName(this TypeReference @this) - { - switch (@this.Scope.MetadataScopeType) - { - case MetadataScopeType.AssemblyNameReference: - return ((AssemblyNameReference)@this.Scope).FullName; - case MetadataScopeType.ModuleReference: - return ((ModuleReference)@this.Scope).Name; - default: - return ((ModuleDefinition)@this.Scope).Assembly.FullName; + var definition = @this.Resolve(); + return definition != null && TypeDefinitionExtensions.IsImplementorOf(definition, targetInterfaces); } - } - - public static string ToFriendlyString(this TypeReference type) - { - if (!string.IsNullOrEmpty(type.Namespace)) + catch { - return type.ToString().Replace(type.Namespace, string.Empty).TrimStart('.'); + // Unity may fail to resolve some of its assemblies, but that's okay + // they don't contain RealmObject classes. } - return type.ToString(); + return false; } } diff --git a/Realm/Realm.Weaver/ILogger.cs b/Realm/Realm.Weaver/ILogger.cs index 19c9a25597..c29ae76e93 100644 --- a/Realm/Realm.Weaver/ILogger.cs +++ b/Realm/Realm.Weaver/ILogger.cs @@ -26,8 +26,8 @@ internal interface ILogger void Info(string message); - void Warning(string message, SequencePoint sequencePoint = null); + void Warning(string message, SequencePoint? sequencePoint = null); - void Error(string message, SequencePoint sequencePoint = null); + void Error(string message, SequencePoint? sequencePoint = null); } } diff --git a/Realm/Realm.Weaver/ImportedReferences.cs b/Realm/Realm.Weaver/ImportedReferences.cs index b8a0a1a9d9..1aa113a0f2 100644 --- a/Realm/Realm.Weaver/ImportedReferences.cs +++ b/Realm/Realm.Weaver/ImportedReferences.cs @@ -17,9 +17,11 @@ //////////////////////////////////////////////////////////////////////////// using System; -using System.Linq; using Mono.Cecil; +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable InconsistentNaming namespace RealmWeaver { #pragma warning disable CS0618 // Type or member is obsolete - Mono.Cecil.TypeSystem is obsolete, but we need it in Unity @@ -30,22 +32,12 @@ internal abstract class ImportedReferences public TypeReference ICollectionOfT { get; } - public MethodReference ICollectionOfT_Add { get; private set; } - - public MethodReference ICollectionOfT_Clear { get; private set; } - - public MethodReference ICollectionOfT_get_Count { get; private set; } - public TypeReference IListOfT { get; } - public MethodReference IListOfT_get_Item { get; private set; } - public abstract TypeReference ISetOfT { get; } public TypeReference IDictionaryOfTKeyTValue { get; } - public MethodReference ISetOfT_UnionWith { get; private set; } - public abstract TypeReference IQueryableOfT { get; } public TypeReference System_Type { get; } @@ -68,17 +60,11 @@ internal abstract class ImportedReferences public TypeReference System_ValueType { get; } - public TypeReference System_IFormattable { get; } - public TypeReference System_IComparableOfT { get; } public TypeReference System_NullableOfT { get; } - public MethodReference System_NullableOfT_GetValueOrDefault { get; } - - public MethodReference System_NullableOfT_Ctor { get; } - - public TypeReference Realm { get; private set; } + public TypeReference? Realm { get; private set; } public MethodReference Realm_Add { get; private set; } @@ -90,6 +76,10 @@ internal abstract class ImportedReferences public TypeReference IRealmObjectBase { get; private set; } + public TypeReference IEmbeddedObject { get; private set; } + + public TypeReference IAsymmetricObject { get; private set; } + public TypeReference ManagedAccessor { get; private set; } public TypeReference EmbeddedObject { get; private set; } @@ -146,17 +136,19 @@ internal abstract class ImportedReferences public TypeReference RealmSchema_PropertyType { get; private set; } - public TypeReference SyncConfiguration { get; private set; } - public MethodReference CollectionExtensions_PopulateCollection { get; private set; } public MethodReference CollectionExtensions_PopulateDictionary { get; private set; } + public TypeReference SyncSession { get; private set; } + protected ModuleDefinition Module { get; } public TypeSystem Types { get; } +#pragma warning disable CS8618 protected ImportedReferences(ModuleDefinition module, TypeSystem types) +#pragma warning restore CS8618 { Module = module; Types = types; @@ -176,8 +168,6 @@ protected ImportedReferences(ModuleDefinition module, TypeSystem types) System_ValueType = new TypeReference("System", "ValueType", Module, Module.TypeSystem.CoreLibrary); - System_IFormattable = new TypeReference("System", "IFormattable", Module, Module.TypeSystem.CoreLibrary); - System_IComparableOfT = new TypeReference("System", "IComparable`1", Module, Module.TypeSystem.CoreLibrary); System_IComparableOfT.GenericParameters.Add(new GenericParameter(System_IComparableOfT)); @@ -187,17 +177,6 @@ protected ImportedReferences(ModuleDefinition module, TypeSystem types) Constraints = { new GenericParameterConstraint(System_ValueType) } }); - System_NullableOfT_GetValueOrDefault = new MethodReference("GetValueOrDefault", System_NullableOfT.GenericParameters[0], System_NullableOfT) - { - HasThis = true - }; - - System_NullableOfT_Ctor = new MethodReference(".ctor", Types.Void, System_NullableOfT) - { - HasThis = true, - Parameters = { new ParameterDefinition(System_NullableOfT.GenericParameters[0]) } - }; - var runtimeTypeHandle = new TypeReference("System", "RuntimeTypeHandle", Module, Module.TypeSystem.CoreLibrary) { IsValueType = true @@ -222,28 +201,6 @@ public MethodReference RealmValue_op_Implicit(TypeReference targetType) private void InitializeFrameworkMethods() { - ICollectionOfT_Add = new MethodReference("Add", Types.Void, ICollectionOfT) - { - HasThis = true, - Parameters = { new ParameterDefinition(ICollectionOfT.GenericParameters.Single()) } - }; - - ICollectionOfT_Clear = new MethodReference("Clear", Types.Void, ICollectionOfT) { HasThis = true }; - - ICollectionOfT_get_Count = new MethodReference("get_Count", Types.Int32, ICollectionOfT) { HasThis = true }; - - IListOfT_get_Item = new MethodReference("get_Item", IListOfT.GenericParameters.Single(), IListOfT) - { - HasThis = true, - Parameters = { new ParameterDefinition(Types.Int32) } - }; - - ISetOfT_UnionWith = new MethodReference("UnionWith", Types.Void, ISetOfT) - { - HasThis = true, - Parameters = { new ParameterDefinition(new GenericInstanceType(IEnumerableOfT) { GenericArguments = { ISetOfT.GenericParameters.Single() } }) } - }; - { System_Linq_Enumerable_Empty = new MethodReference("Empty", Types.Void, System_Linq_Enumerable); var T = new GenericParameter(System_Linq_Enumerable_Empty); @@ -271,6 +228,8 @@ private void InitializeRealm(IMetadataScope realmAssembly) AsymmetricObject = new TypeReference("Realms", "AsymmetricObject", Module, realmAssembly); RealmSchema_PropertyType = new TypeReference("Realms.Schema", "PropertyType", Module, realmAssembly, valueType: true); RealmValue = new TypeReference("Realms", "RealmValue", Module, realmAssembly, valueType: true); + IEmbeddedObject = new TypeReference("Realms", "IEmbeddedObject", Module, realmAssembly, valueType: false); + IAsymmetricObject = new TypeReference("Realms", "IAsymmetricObject", Module, realmAssembly, valueType: false); RealmValue_GetNull = new MethodReference("get_Null", RealmValue, RealmValue) { HasThis = false }; { @@ -301,7 +260,7 @@ private void InitializeRealm(IMetadataScope realmAssembly) { RealmObject_GetListValue = new MethodReference("GetListValue", new GenericInstanceType(IListOfT), RealmObjectBase) { HasThis = true }; var T = new GenericParameter(RealmObject_GetListValue); - (RealmObject_GetListValue.ReturnType as GenericInstanceType).GenericArguments.Add(T); + (RealmObject_GetListValue.ReturnType as GenericInstanceType)!.GenericArguments.Add(T); RealmObject_GetListValue.GenericParameters.Add(T); RealmObject_GetListValue.Parameters.Add(new ParameterDefinition(Types.String)); } @@ -309,7 +268,7 @@ private void InitializeRealm(IMetadataScope realmAssembly) { RealmObject_GetSetValue = new MethodReference("GetSetValue", new GenericInstanceType(ISetOfT), RealmObjectBase) { HasThis = true }; var T = new GenericParameter(RealmObject_GetSetValue); - (RealmObject_GetSetValue.ReturnType as GenericInstanceType).GenericArguments.Add(T); + (RealmObject_GetSetValue.ReturnType as GenericInstanceType)!.GenericArguments.Add(T); RealmObject_GetSetValue.GenericParameters.Add(T); RealmObject_GetSetValue.Parameters.Add(new ParameterDefinition(Types.String)); } @@ -317,8 +276,8 @@ private void InitializeRealm(IMetadataScope realmAssembly) { RealmObject_GetDictionaryValue = new MethodReference("GetDictionaryValue", new GenericInstanceType(IDictionaryOfTKeyTValue), RealmObjectBase) { HasThis = true }; var TValue = new GenericParameter(RealmObject_GetSetValue); - (RealmObject_GetDictionaryValue.ReturnType as GenericInstanceType).GenericArguments.Add(Types.String); - (RealmObject_GetDictionaryValue.ReturnType as GenericInstanceType).GenericArguments.Add(TValue); + (RealmObject_GetDictionaryValue.ReturnType as GenericInstanceType)!.GenericArguments.Add(Types.String); + (RealmObject_GetDictionaryValue.ReturnType as GenericInstanceType)!.GenericArguments.Add(TValue); RealmObject_GetDictionaryValue.GenericParameters.Add(TValue); RealmObject_GetDictionaryValue.Parameters.Add(new ParameterDefinition(Types.String)); } @@ -326,14 +285,17 @@ private void InitializeRealm(IMetadataScope realmAssembly) { RealmObject_GetBacklinks = new MethodReference("GetBacklinks", new GenericInstanceType(IQueryableOfT), RealmObjectBase) { HasThis = true }; var T = new GenericParameter(RealmObject_GetBacklinks) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } }; - (RealmObject_GetBacklinks.ReturnType as GenericInstanceType).GenericArguments.Add(T); + (RealmObject_GetBacklinks.ReturnType as GenericInstanceType)!.GenericArguments.Add(T); RealmObject_GetBacklinks.GenericParameters.Add(T); RealmObject_GetBacklinks.Parameters.Add(new ParameterDefinition(Types.String)); } { - RealmObject_GetValue = new MethodReference("GetValue", Types.Void, RealmObjectBase) { HasThis = true }; - RealmObject_GetValue.ReturnType = RealmValue; + RealmObject_GetValue = new MethodReference("GetValue", Types.Void, RealmObjectBase) + { + HasThis = true, + ReturnType = RealmValue + }; RealmObject_GetValue.Parameters.Add(new ParameterDefinition(Types.String)); } @@ -385,7 +347,7 @@ private void InitializeRealm(IMetadataScope realmAssembly) RealmSchema_AddDefaultTypes.Parameters.Add(new ParameterDefinition(ienumerableOfType)); } - SyncConfiguration = new TypeReference("Realms.Sync", "SyncConfiguration", Module, realmAssembly); + SyncSession = new TypeReference("Realms.Sync", "Session", Module, realmAssembly); var collectionExtensions = new TypeReference("Realms", "CollectionExtensions", Module, realmAssembly); CollectionExtensions_PopulateCollection = new MethodReference("PopulateCollection", Types.Void, collectionExtensions) { HasThis = false }; diff --git a/Realm/Realm.Weaver/Realm.Weaver.shproj b/Realm/Realm.Weaver/Realm.Weaver.shproj index 166ed87f82..91ea0bce33 100644 --- a/Realm/Realm.Weaver/Realm.Weaver.shproj +++ b/Realm/Realm.Weaver/Realm.Weaver.shproj @@ -3,11 +3,11 @@ 89fdb2a1-6fc0-4604-9aeb-8b8dd8a88dec 14.0 + enable - diff --git a/Realm/Realm.Weaver/RealmWeaver.Schema.cs b/Realm/Realm.Weaver/RealmWeaver.Schema.cs index e8afacac3e..42c64dd3de 100644 --- a/Realm/Realm.Weaver/RealmWeaver.Schema.cs +++ b/Realm/Realm.Weaver/RealmWeaver.Schema.cs @@ -31,7 +31,7 @@ internal partial class Weaver private void WeaveSchema(TypeDefinition[] types) { - if (_references.RealmSchema_AddDefaultTypes == null) + if (_references.Realm == null) { // Realm is added, but not used, so we don't need to weave schema return; @@ -74,7 +74,7 @@ private void WeaveSchema(TypeDefinition[] types) } } - private IEnumerable GetReferencedTypes(ModuleDefinition module = null, HashSet processedAssemblies = null) + private IEnumerable GetReferencedTypes(ModuleDefinition? module = null, HashSet? processedAssemblies = null) { module ??= _moduleDefinition; diff --git a/Realm/Realm.Weaver/RealmWeaver.cs b/Realm/Realm.Weaver/RealmWeaver.cs index a6a1fdab79..1daf14fd5f 100644 --- a/Realm/Realm.Weaver/RealmWeaver.cs +++ b/Realm/Realm.Weaver/RealmWeaver.cs @@ -18,14 +18,14 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using Realms; +// ReSharper disable InconsistentNaming +// ReSharper disable MemberCanBePrivate.Global namespace RealmWeaver { /// @@ -65,7 +65,7 @@ internal partial class Weaver internal const string NullableObjectIdTypeName = "System.Nullable`1"; internal const string NullableGuidTypeName = "System.Nullable`1"; - private static readonly HashSet _realmValueTypes = new HashSet + private static readonly HashSet _realmValueTypes = new() { CharTypeName, SingleTypeName, @@ -125,7 +125,7 @@ internal partial class Weaver NullableGuidTypeName }; - private static readonly HashSet RealmPropertyAttributes = new HashSet + private static readonly HashSet RealmPropertyAttributes = new() { "PrimaryKeyAttribute", "IndexedAttribute", @@ -182,8 +182,15 @@ public WeaveModuleResult Execute(Analytics.Config analyticsConfig) { _logger.Debug("Weaving file: " + _moduleDefinition.FileName); - if (_references.WovenAssemblyAttribute == null) + var analytics = new Analytics(analyticsConfig, _references, _logger, _moduleDefinition); + + // This is necessary because some frameworks, e.g. xamarin, have the models in one assembly and the platform + // specific code in another assembly, but we still want to report what target the user is building for + if (_references.Realm == null) { + // Don't wait for submission + _ = analytics.SubmitAnalytics(); + return WeaveModuleResult.Skipped($"Not weaving assembly '{_moduleDefinition.Assembly.Name}' because it doesn't reference Realm."); } @@ -193,28 +200,6 @@ public WeaveModuleResult Execute(Analytics.Config analyticsConfig) return WeaveModuleResult.Skipped($"Not weaving assembly '{_moduleDefinition.Assembly.Name}' because it has already been processed."); } - var submitAnalytics = Task.Run(() => - { - analyticsConfig.ModuleName = _moduleDefinition.Name; - analyticsConfig.IsUsingSync = IsUsingSync(); - var analytics = new Analytics(analyticsConfig); - try - { - var payload = analytics.SubmitAnalytics(); -#if DEBUG - _logger.Info($@" ----------------------------------- -Analytics payload -{payload} -----------------------------------"); -#endif - } - catch (Exception e) - { - _logger.Debug("Error submitting analytics: " + e.Message); - } - }); - var matchingTypes = GetMatchingTypes().ToArray(); var weaveResults = matchingTypes.Select(matchingType => @@ -238,9 +223,12 @@ Analytics payload var wovenAssemblyAttribute = new CustomAttribute(_references.WovenAssemblyAttribute_Constructor); _moduleDefinition.Assembly.CustomAttributes.Add(wovenAssemblyAttribute); - submitAnalytics.Wait(); + analytics.AnalyzeRealmClassProperties(weaveResults); - var failedResults = weaveResults.Where(r => !r.IsSuccessful); + // Don't wait for submission + _ = analytics.SubmitAnalytics(); + + var failedResults = weaveResults.Where(r => !r.IsSuccessful).ToArray(); if (failedResults.Any()) { return WeaveModuleResult.Error($"The following types had errors when woven: {string.Join(", ", failedResults.Select(f => f.Type))}"); @@ -270,7 +258,7 @@ private static void RemoveBackingFields(TypeDefinition type, HashSet" - // it considers this the end index of backing field initializaion instructions. + // it considers this the end index of backing field initialization instructions. if (instruction.OpCode == OpCodes.Stfld && instruction.Operand is FieldReference field) { if (backingFields.Contains(field.MetadataToken)) @@ -280,7 +268,7 @@ private static void RemoveBackingFields(TypeDefinition type, HashSet Accessor.Property = value; - // Whilst we're only targetting auto-properties here, someone like PropertyChanged.Fody + // Whilst we're only targeting auto-properties here, someone like PropertyChanged.Fody // may have already come in and rewritten our IL. Lets clear everything and start from scratch. var il = prop.SetMethod.Body.GetILProcessor(); prop.SetMethod.Body.Instructions.Clear(); @@ -421,7 +409,7 @@ private WeaveTypeResult WeaveType(TypeDefinition type) var didSucceed = true; var persistedProperties = new List(); - foreach (var prop in type.Properties.Where(x => x.HasThis && !x.CustomAttributes.Any(a => a.AttributeType.Name == "IgnoredAttribute"))) + foreach (var prop in type.Properties.Where(x => x.HasThis && x.CustomAttributes.All(a => a.AttributeType.Name != "IgnoredAttribute"))) { try { @@ -433,7 +421,7 @@ private WeaveTypeResult WeaveType(TypeDefinition type) else { var sequencePoint = prop.GetSequencePoint(); - if (!string.IsNullOrEmpty(weaveResult.ErrorMessage)) + if (!weaveResult.ErrorMessage.IsNullOrEmpty()) { // We only want one error point, so even though there may be more problems, we only log the first one. _logger.Error(weaveResult.ErrorMessage, sequencePoint); @@ -443,7 +431,7 @@ private WeaveTypeResult WeaveType(TypeDefinition type) } else { - if (!string.IsNullOrEmpty(weaveResult.WarningMessage)) + if (!weaveResult.WarningMessage.IsNullOrEmpty()) { _logger.Warning(weaveResult.WarningMessage, sequencePoint); } @@ -452,7 +440,8 @@ private WeaveTypeResult WeaveType(TypeDefinition type) .Select(a => a.AttributeType.Name) .Intersect(RealmPropertyAttributes) .OrderBy(a => a) - .Select(a => $"[{a.Replace("Attribute", string.Empty)}]"); + .Select(a => $"[{a.Replace("Attribute", string.Empty)}]") + .ToArray(); if (realmAttributeNames.Any()) { @@ -481,7 +470,7 @@ private WeaveTypeResult WeaveType(TypeDefinition type) var pkProperty = persistedProperties.FirstOrDefault(p => p.IsPrimaryKey); if (type.IsEmbeddedObjectInheritor(_references) && pkProperty != null) { - _logger.Error($"Class {type.Name} is an EmbeddedObject but has a primary key {pkProperty.Property.Name} defined.", type.GetSequencePoint()); + _logger.Error($"Class {type.Name} is an EmbeddedObject but has a primary key {pkProperty.Property!.Name} defined.", type.GetSequencePoint()); return WeaveTypeResult.Error(type.Name); } @@ -531,7 +520,6 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Skipped(); } - var backingField = prop.GetBackingField(); var indexedAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "IndexedAttribute"); if (indexedAttribute != null) { @@ -543,14 +531,12 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio if (indexedAttribute.ConstructorArguments.Count > 0) { var mode = (IndexType)(int)indexedAttribute.ConstructorArguments[0].Value; - if (mode == IndexType.None) + switch (mode) { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed(IndexType.None)] which is not allowed. If you don't wish to index the property, remove the IndexedAttribute."); - } - - if (mode == IndexType.FullText && prop.PropertyType.FullName != StringTypeName) - { - return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed(IndexType.FullText)] which is only allowed on string properties, not on {prop.PropertyType.FullName}."); + case IndexType.None: + return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed(IndexType.None)] which is not allowed. If you don't wish to index the property, remove the IndexedAttribute."); + case IndexType.FullText when prop.PropertyType.FullName != StringTypeName: + return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed(IndexType.FullText)] which is only allowed on string properties, not on {prop.PropertyType.FullName}."); } } } @@ -593,6 +579,12 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Error($"{type.Name}.{prop.Name} has [Backlink] applied, but is not IQueryable."); } + var backingField = prop.GetBackingField(); + if (backingField == null) + { + return WeavePropertyResult.Warning($"{type.Name}.{prop.Name} is an automatic property without a backing field."); + } + if (_realmValueTypes.Contains(prop.PropertyType.FullName)) { if (prop.SetMethod == null) @@ -881,7 +873,7 @@ private void ReplaceBacklinksGetter(PropertyDefinition prop, FieldReference back il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); // this for call [ this -> this, this] il.InsertBefore(start, il.Create(OpCodes.Call, _references.RealmObject_get_IsManaged)); // [ this, this -> this, isManaged ] - // push in the label then go relative to that - so we can forward-ref the lable insert if/else blocks backwards + // push in the label then go relative to that - so we can forward-ref the label insert if/else blocks backwards var labelElse = il.Create(OpCodes.Nop); // [this] il.InsertBefore(start, labelElse); // else il.InsertBefore(start, il.Create(OpCodes.Call, new GenericInstanceMethod(_references.System_Linq_Enumerable_Empty) { GenericArguments = { elementType } })); // [this, enumerable] @@ -936,7 +928,7 @@ private void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, throw new ArgumentNullException(nameof(setValueReference)); } - // Whilst we're only targetting auto-properties here, someone like PropertyChanged.Fody + // Whilst we're only targeting auto-properties here, someone like PropertyChanged.Fody // may have already come in and rewritten our IL. Lets clear everything and start from scratch. var il = prop.SetMethod.Body.GetILProcessor(); prop.SetMethod.Body.Instructions.Clear(); @@ -1012,7 +1004,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me var castInstance = (ObjectType)instance; *foreach* non-list woven property in castInstance's schema - *if* castInstace.field is a RealmObject descendant + *if* castInstance.field is a RealmObject descendant castInstance.Realm.Add(castInstance.field, update); castInstance.Property = castInstance.Field; *else if* property is PK @@ -1073,10 +1065,10 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me il.Append(il.Create(OpCodes.Stloc_0)); // We'll process collections separately as those require variable access - foreach (var prop in properties.Where(p => !p.IsPrimaryKey && !p.Property.IsCollection(out _))) + foreach (var prop in properties.Where(p => !p.IsPrimaryKey && !p.Property!.IsCollection(out _))) { - var property = prop.Property; - var field = prop.Field; + var property = prop.Property!; + var field = prop.Field!; if (property.SetMethod != null) { @@ -1088,7 +1080,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me // castInstance.Property = castInstance.field; // // *addPlaceholder* will be the Brfalse instruction that will skip the call to Add if the field is null. - Instruction addPlaceholder = null; + Instruction? addPlaceholder = null; // We can skip setting properties that have their default values unless: var shouldSetAlways = property.IsNullable() || // The property is nullable - those should be set explicitly to null @@ -1110,7 +1102,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me // *updatePlaceholder* will be the Brtrue instruction that will skip the default check and move to the // property setting logic. The default check branching instruction is inserted above the *setStartPoint* // instruction later on. - Instruction skipDefaultsPlaceholder = null; + Instruction? skipDefaultsPlaceholder = null; if (property.ContainsRealmObject(_references)) { il.Append(il.Create(OpCodes.Ldloc_0)); @@ -1196,7 +1188,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me // Process collection properties foreach (var prop in properties) { - if (!prop.Property.IsCollection(out var collectionType)) + if (prop.Property?.IsCollection(out var collectionType) != true) { continue; } @@ -1254,7 +1246,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Castclass, _moduleDefinition.ImportReference(realmObjectType)); - il.Emit(OpCodes.Callvirt, _moduleDefinition.ImportReference(pkProperty.Property.GetMethod)); + il.Emit(OpCodes.Callvirt, _moduleDefinition.ImportReference(pkProperty.Property!.GetMethod)); il.Emit(OpCodes.Call, _references.RealmValue_op_Implicit(pkProperty.Property.PropertyType)); } else @@ -1290,29 +1282,6 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me return helperType; } - private bool IsUsingSync() - { - try - { - return IsMethodUsed(_references.SyncConfiguration); - } - catch - { - return false; - } - } - - private bool IsMethodUsed(TypeReference type) - { - return _moduleDefinition.GetTypes() - .SelectMany(t => t.Methods) - .Where(m => m.HasBody) - .SelectMany(m => m.Body.Instructions) - .Any(i => i.OpCode == OpCodes.Newobj && - i.Operand is MethodReference mRef && - mRef.ConstructsType(type)); - } - private MethodReference GetOrAddPropertyChanged_DoNotNotify() { // If the assembly has a reference to PropertyChanged.Fody, let's look up the DoNotNotifyAttribute for use later. @@ -1348,7 +1317,7 @@ private MethodReference GetOrAddPropertyChanged_DoNotNotify() return userAssembly_DoNotNotify_Ctor; } - private struct MatchingType + private readonly struct MatchingType { public bool IsGenerated { get; } diff --git a/Realm/Realm.Weaver/WeaveResults.cs b/Realm/Realm.Weaver/WeaveResults.cs index 9c9ea51a50..87a363bb77 100644 --- a/Realm/Realm.Weaver/WeaveResults.cs +++ b/Realm/Realm.Weaver/WeaveResults.cs @@ -17,10 +17,12 @@ //////////////////////////////////////////////////////////////////////////// using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using Mono.Cecil; +// ReSharper disable MemberCanBePrivate.Global namespace RealmWeaver { internal class WeaveModuleResult @@ -40,13 +42,13 @@ public static WeaveModuleResult Skipped(string reason) return new WeaveModuleResult(skipReason: reason); } - public WeaveTypeResult[] Types { get; } + public WeaveTypeResult[]? Types { get; } - public string SkipReason { get; } + public string? SkipReason { get; } - public string ErrorMessage { get; } + public string? ErrorMessage { get; } - private WeaveModuleResult(WeaveTypeResult[] types = null, string skipReason = null, string errorMessage = null) + private WeaveModuleResult(WeaveTypeResult[]? types = null, string? skipReason = null, string? errorMessage = null) { Types = types; SkipReason = skipReason; @@ -66,7 +68,7 @@ public override string ToString() } var sb = new StringBuilder(); - var wovenMessage = Types.Length == 1 ? "class was" : "classes were"; + var wovenMessage = Types!.Length == 1 ? "class was" : "classes were"; sb.AppendLine($"{Types.Length} {wovenMessage} woven:"); foreach (var type in Types) { @@ -91,13 +93,14 @@ public static WeaveTypeResult Error(string type, bool isGenerated = false) public string Type { get; } + [MemberNotNullWhen(true, nameof(Properties))] public bool IsSuccessful { get; } public bool IsGenerated { get; } - public WeavePropertyResult[] Properties { get; } + public WeavePropertyResult[]? Properties { get; } - private WeaveTypeResult(string type, WeavePropertyResult[] properties = null, bool success = true, bool isGenerated = false) + private WeaveTypeResult(string type, WeavePropertyResult[]? properties = null, bool success = true, bool isGenerated = false) { Properties = properties; Type = type; @@ -152,21 +155,24 @@ public static WeavePropertyResult Skipped() return new WeavePropertyResult(); } - public string ErrorMessage { get; } + public string? ErrorMessage { get; } - public string WarningMessage { get; } + public string? WarningMessage { get; } + [MemberNotNullWhen(true, nameof(Property))] public bool Woven { get; } - public PropertyDefinition Property { get; } + public PropertyDefinition? Property { get; } - public FieldReference Field { get; } + public FieldReference? Field { get; } + [MemberNotNullWhen(true, nameof(Property))] public bool IsPrimaryKey { get; } + [MemberNotNullWhen(true, nameof(Property))] public bool IsIndexed { get; } - private WeavePropertyResult(PropertyDefinition property, FieldReference field, bool isPrimaryKey, bool isIndexed) + private WeavePropertyResult(PropertyDefinition property, FieldReference? field, bool isPrimaryKey, bool isIndexed) { Property = property; Field = field; @@ -175,7 +181,7 @@ private WeavePropertyResult(PropertyDefinition property, FieldReference field, b Woven = true; } - private WeavePropertyResult(string error = null, string warning = null) + private WeavePropertyResult(string? error = null, string? warning = null) { ErrorMessage = error; WarningMessage = warning; @@ -183,7 +189,7 @@ private WeavePropertyResult(string error = null, string warning = null) public override string ToString() { - return $" {Property.Name}: {Property.PropertyType.ToFriendlyString()}{(IsPrimaryKey ? " [PrimaryKey]" : string.Empty)}{(IsIndexed ? " [Indexed]" : string.Empty)}"; + return $" {Property?.Name}: {Property?.PropertyType.ToFriendlyString()}{(IsPrimaryKey ? " [PrimaryKey]" : string.Empty)}{(IsIndexed ? " [Indexed]" : string.Empty)}"; } } } diff --git a/Realm/Realm/Handles/AppHandle.cs b/Realm/Realm/Handles/AppHandle.cs index 762d4e2c2a..7221a42521 100644 --- a/Realm/Realm/Handles/AppHandle.cs +++ b/Realm/Realm/Handles/AppHandle.cs @@ -55,13 +55,14 @@ public static extern IntPtr initialize( [MarshalAs(UnmanagedType.LPWStr)] string platform_version, IntPtr platform_version_len, [MarshalAs(UnmanagedType.LPWStr)] string device_name, IntPtr device_name_len, [MarshalAs(UnmanagedType.LPWStr)] string device_version, IntPtr device_version_len, + [MarshalAs(UnmanagedType.LPWStr)] string bundle_id, IntPtr bundle_id_len, UserCallback user_callback, VoidTaskCallback void_callback, StringCallback string_callback, ApiKeysCallback api_keys_callback); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_create", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_app(Native.AppConfiguration app_config, byte[]? encryptionKey, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_destroy", CallingConvention = CallingConvention.Cdecl)] - public static extern void destroy(IntPtr syncuserHandle); + public static extern void destroy(IntPtr syncUserHandle); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_sync_immediately_run_file_actions", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] @@ -173,16 +174,19 @@ public static void Initialize() string deviceName; string deviceVersion; + string bundleId; try { deviceName = Platform.DeviceInfo.Name; deviceVersion = Platform.DeviceInfo.Version; + bundleId = Platform.BundleId; } catch { // If we can't get the device info, don't crash the app. deviceName = Platform.Unknown; deviceVersion = Platform.Unknown; + bundleId = Platform.Unknown; #if DEBUG throw; @@ -196,6 +200,7 @@ public static void Initialize() platformVersion, platformVersion.IntPtrLength(), deviceName, deviceName.IntPtrLength(), deviceVersion, deviceVersion.IntPtrLength(), + bundleId, bundleId.IntPtrLength(), userLogin, taskCallback, stringCallback, apiKeysCallback); } diff --git a/Realm/Realm/Sync/Credentials.cs b/Realm/Realm/Sync/Credentials.cs index 29b9ba2f2d..4c4680e914 100644 --- a/Realm/Realm/Sync/Credentials.cs +++ b/Realm/Realm/Sync/Credentials.cs @@ -175,7 +175,7 @@ public static Credentials EmailPassword(string email, string password) } /// - /// Creates credentials represetning a login with Realm function. + /// Creates credentials representing a login with Realm function. /// /// The payload that will be passed as an argument to the server function. /// A Credentials that can be used to authenticate a user by invoking a server function. diff --git a/Tests/Benchmarks/Benchmarks/FodyWeavers.xml b/Tests/Benchmarks/Benchmarks/FodyWeavers.xml index f8f347b9d6..3e1a999a33 100644 --- a/Tests/Benchmarks/Benchmarks/FodyWeavers.xml +++ b/Tests/Benchmarks/Benchmarks/FodyWeavers.xml @@ -1,4 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml b/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml index f8f347b9d6..3e1a999a33 100644 --- a/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml +++ b/Tests/Benchmarks/PerformanceTests/FodyWeavers.xml @@ -1,4 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/Realm.Tests/FodyWeavers.xml b/Tests/Realm.Tests/FodyWeavers.xml index cda31f6046..6769f96097 100644 --- a/Tests/Realm.Tests/FodyWeavers.xml +++ b/Tests/Realm.Tests/FodyWeavers.xml @@ -1,4 +1,4 @@ - - - + + \ No newline at end of file diff --git a/Tests/Realm.Tests/Sync/AppTests.cs b/Tests/Realm.Tests/Sync/AppTests.cs index 021d5517b4..1fe31b1ce6 100644 --- a/Tests/Realm.Tests/Sync/AppTests.cs +++ b/Tests/Realm.Tests/Sync/AppTests.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -38,10 +39,17 @@ public class AppTests : SyncTestBase [Test] public void DeviceInfo_OutputsMeaningfulInfo() { + void AssertBundleId(params string[] expectedValues) + { + var values = expectedValues.Concat(new[] { "ReSharperTestRunner" }).Select(Platform.Sha256).ToArray(); + Assert.That(values, Does.Contain(Platform.BundleId)); + } + if (TestHelpers.IsUnity) { Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown)); Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown)); + Assert.That(Platform.BundleId, Is.EqualTo(Platform.Sha256("Tests.Unity"))); return; } @@ -54,6 +62,7 @@ public void DeviceInfo_OutputsMeaningfulInfo() case "Linux": Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown), "Name"); Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); + AssertBundleId("Realm.Tests"); break; case "macOS": // We don't detect the device on .NET Core apps, only Xamarin or net6.0-maccatalyst. @@ -61,21 +70,25 @@ public void DeviceInfo_OutputsMeaningfulInfo() { Assert.That(Platform.DeviceInfo.Name, Is.EqualTo(Platform.Unknown), "Name"); Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); + AssertBundleId("Realm.Tests"); } else { Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("Apple"), "Name"); Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown), "Version"); + AssertBundleId("Tests.XamarinMac"); } break; case "iOS": Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("iPhone"), "Name"); Assert.That(Platform.DeviceInfo.Version, Does.Contain("iPhone").Or.EqualTo("x86_64"), "Version"); + AssertBundleId("Tests.iOS", "Tests.Maui"); break; case "Android": Assert.That(Platform.DeviceInfo.Name, Is.Not.EqualTo(Platform.Unknown), "Name"); Assert.That(Platform.DeviceInfo.Version, Is.Not.EqualTo(Platform.Unknown), "Version"); + AssertBundleId("Tests.Android", "Tests.Maui"); break; case "UWP": if (TestHelpers.IsUWP) @@ -93,14 +106,17 @@ public void DeviceInfo_OutputsMeaningfulInfo() Assert.That(Platform.DeviceInfo.Version, Is.EqualTo(Platform.Unknown), "Version"); } + AssertBundleId("Tests.UWP"); break; case "tvOS": Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("Apple TV"), "Name"); Assert.That(Platform.DeviceInfo.Version, Does.Contain("AppleTV").Or.EqualTo("x86_64"), "Version"); + AssertBundleId("Tests.XamarinTVOS"); break; case "Mac Catalyst": Assert.That(Platform.DeviceInfo.Name, Is.EqualTo("iPad"), "Name"); Assert.That(Platform.DeviceInfo.Version, Does.Contain("iPad").Or.EqualTo("x86_64"), "Version"); + AssertBundleId("Tests.Maui"); break; default: Assert.Fail($"Unknown OS: {os}"); diff --git a/Tests/SourceGenerators/Realm.SourceGenerator.Tests/FodyWeavers.xml b/Tests/SourceGenerators/Realm.SourceGenerator.Tests/FodyWeavers.xml index 9918007d4d..c56c82c21a 100644 --- a/Tests/SourceGenerators/Realm.SourceGenerator.Tests/FodyWeavers.xml +++ b/Tests/SourceGenerators/Realm.SourceGenerator.Tests/FodyWeavers.xml @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/FodyWeavers.xml b/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/FodyWeavers.xml index 9de070df00..faffd355b3 100644 --- a/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/FodyWeavers.xml +++ b/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/FodyWeavers.xml @@ -1,4 +1,5 @@  - - + + \ No newline at end of file diff --git a/Tests/Tests.Android/Tests.Android.csproj b/Tests/Tests.Android/Tests.Android.csproj index d0ec78979c..2ef73289f0 100644 --- a/Tests/Tests.Android/Tests.Android.csproj +++ b/Tests/Tests.Android/Tests.Android.csproj @@ -44,6 +44,26 @@ true false + + true + bin\x64\Debug\ + DEBUG; + portable + x64 + Off + 9.0 + prompt + + + true + bin\x64\Release\ + true + portable + x64 + Off + 9.0 + prompt + @@ -88,7 +108,7 @@ Realm - {4C9BE4FA-9CB2-4091-B86A-4EC557A78991} + {D08C71CE-0F5B-4855-A77C-58062A5ECB78} Realm.PlatformHelpers @@ -112,4 +132,9 @@ + + + + + \ No newline at end of file diff --git a/Tests/Tests.UWP/FodyWeavers.xml b/Tests/Tests.UWP/FodyWeavers.xml index 2ef23e7053..c56c82c21a 100644 --- a/Tests/Tests.UWP/FodyWeavers.xml +++ b/Tests/Tests.UWP/FodyWeavers.xml @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/Tests/Tests.XUnit/FodyWeavers.xml b/Tests/Tests.XUnit/FodyWeavers.xml index 9918007d4d..c56c82c21a 100644 --- a/Tests/Tests.XUnit/FodyWeavers.xml +++ b/Tests/Tests.XUnit/FodyWeavers.xml @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/Tests/Tests.XamarinMac/Tests.XamarinMac.csproj b/Tests/Tests.XamarinMac/Tests.XamarinMac.csproj index cd8a517c7f..27e5af0d2a 100644 --- a/Tests/Tests.XamarinMac/Tests.XamarinMac.csproj +++ b/Tests/Tests.XamarinMac/Tests.XamarinMac.csproj @@ -95,7 +95,7 @@ - {4C9BE4FA-9CB2-4091-B86A-4EC557A78991} + {D08C71CE-0F5B-4855-A77C-58062A5ECB78} Realm.PlatformHelpers diff --git a/Tests/Tests.iOS/Tests.iOS.csproj b/Tests/Tests.iOS/Tests.iOS.csproj index d5b02bc101..840776ee44 100644 --- a/Tests/Tests.iOS/Tests.iOS.csproj +++ b/Tests/Tests.iOS/Tests.iOS.csproj @@ -164,7 +164,7 @@ Realm - {4C9BE4FA-9CB2-4091-B86A-4EC557A78991} + {D08C71CE-0F5B-4855-A77C-58062A5ECB78} Realm.PlatformHelpers diff --git a/Tests/Weaver/AnalyticsAssembly/AnalyticsAssembly.csproj b/Tests/Weaver/AnalyticsAssembly/AnalyticsAssembly.csproj new file mode 100644 index 0000000000..d9d20a35ed --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/AnalyticsAssembly.csproj @@ -0,0 +1,38 @@ + + + + net472;netcoreapp3.1;net6.0 + 9.0 + true + Generated + true + + + + + + + + + + + + + + + + + + + + + + + $(DefineConstants);I_EMBEDDED_OBJECT;I_ASYMMETRIC_OBJECT;REFERENCE_LIST;PRIMITIVE_LIST;REFERENCE_DICTIONARY;PRIMITIVE_DICTIONARY;REFERENCE_SET;PRIMITIVE_SET;REALM_INTEGER;REALM_OBJECT_REFERENCE;REALM_VALUE;BACKLINK_ATTRIBUTE;GET_INSTANCE_ASYNC;GET_INSTANCE;NOT_SUPPORTED_YET;FIND;WRITE_ASYNC;THREAD_SAFE_REFERENCE;FIXME_TWO;SHOULD_COMPACT_ON_LAUNCH;MIGRATION_CALLBACK;REALM_CHANGED;LIST_SUBSCRIBE_FOR_NOTIFICATIONS;SET_SUBSCRIBE_FOR_NOTIFICATIONS;DICTIONARY_SUBSCRIBE_FOR_NOTIFICATIONS;RESULT_SUBSCRIBE_FOR_NOTIFICATIONS;OBJECT_NOTIFICATION;ADD;RECOVER_OR_DISCARD_UNSYNCED_CHANGES_HANDLER;RECOVER_UNSYNCED_CHANGES_HANDLER;DISCARD_UNSYNCED_CHANGES_HANDLER;MANUAL_RECOVERY_HANDLER;GET_PROGRESS_OBSERVABLE;PARTITION_SYNC_CONFIGURATION;FLEXIBLE_SYNC_CONFIGURATION;ANONYMOUS;EMAIL_PASSWORD;FACEBOOK;GOOGLE;APPLE;J_W_T;API_KEY;SERVER_API_KEY;FUNCTION;CALL_ASYNC;GET_MONGO_CLIENT;DYNAMIC_API;CONNECTION_NOTIFICATION; + + + + $(DefineConstants);$(AnalyticsConstants) + + + diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs new file mode 100644 index 0000000000..f2e0d9dbd3 --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/AsymmetricTestClass_generated.cs @@ -0,0 +1,328 @@ +// +#nullable enable + +using Realms; +using Realms.Schema; +using Realms.Sync; +using Realms.Sync.ErrorHandling; +using Realms.Weaving; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml.Serialization; +using TestAsymmetricObject = Realms.IAsymmetricObject; +using TestEmbeddedObject = Realms.IEmbeddedObject; +using TestRealmObject = Realms.IRealmObject; + +[Generated] +[Woven(typeof(AsymmetricTestClassObjectHelper)), Realms.Preserve(AllMembers = true)] +public partial class AsymmetricTestClass : IAsymmetricObject, INotifyPropertyChanged, IReflectableType +{ + /// + /// Defines the schema for the class. + /// + public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("AsymmetricTestClass", ObjectSchema.ObjectType.AsymmetricObject) + { + Realms.Schema.Property.Primitive("Int32Property", Realms.RealmValueType.Int, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "Int32Property"), + }.Build(); + + #region IAsymmetricObject implementation + + private IAsymmetricTestClassAccessor? _accessor; + + Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; + + internal IAsymmetricTestClassAccessor Accessor => _accessor ??= new AsymmetricTestClassUnmanagedAccessor(typeof(AsymmetricTestClass)); + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsManaged => Accessor.IsManaged; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsValid => Accessor.IsValid; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsFrozen => Accessor.IsFrozen; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Realm? Realm => Accessor.Realm; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi; + + /// + [IgnoreDataMember, XmlIgnore] + public int BacklinksCount => Accessor.BacklinksCount; + + void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults) + { + var newAccessor = (IAsymmetricTestClassAccessor)managedAccessor; + var oldAccessor = _accessor; + _accessor = newAccessor; + + if (helper != null && oldAccessor != null) + { + if (!skipDefaults || oldAccessor.Int32Property != default(int)) + { + newAccessor.Int32Property = oldAccessor.Int32Property; + } + } + + if (_propertyChanged != null) + { + SubscribeForNotifications(); + } + + OnManaged(); + } + + #endregion + + /// + /// Called when the object has been managed by a Realm. + /// + /// + /// This method will be called either when a managed object is materialized or when an unmanaged object has been + /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, + /// it is not yet clear whether the object is managed or not. + /// + partial void OnManaged(); + + private event PropertyChangedEventHandler? _propertyChanged; + + /// + public event PropertyChangedEventHandler? PropertyChanged + { + add + { + if (_propertyChanged == null) + { + SubscribeForNotifications(); + } + + _propertyChanged += value; + } + + remove + { + _propertyChanged -= value; + + if (_propertyChanged == null) + { + UnsubscribeFromNotifications(); + } + } + } + + /// + /// Called when a property has changed on this class. + /// + /// The name of the property. + /// + /// For this method to be called, you need to have first subscribed to . + /// This can be used to react to changes to the current object, e.g. raising for computed properties. + /// + /// + /// + /// class MyClass : IRealmObject + /// { + /// public int StatusCodeRaw { get; set; } + /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; + /// partial void OnPropertyChanged(string propertyName) + /// { + /// if (propertyName == nameof(StatusCodeRaw)) + /// { + /// RaisePropertyChanged(nameof(StatusCode)); + /// } + /// } + /// } + /// + /// Here, we have a computed property that depends on a persisted one. In order to notify any + /// subscribers that StatusCode has changed, we implement and + /// raise manually by calling . + /// + partial void OnPropertyChanged(string? propertyName); + + private void RaisePropertyChanged([CallerMemberName] string propertyName = "") + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + OnPropertyChanged(propertyName); + } + + private void SubscribeForNotifications() + { + Accessor.SubscribeForNotifications(RaisePropertyChanged); + } + + private void UnsubscribeFromNotifications() + { + Accessor.UnsubscribeFromNotifications(); + } + + /// + /// Converts a to . Equivalent to . + /// + /// The to convert. + /// The stored in the . + public static explicit operator AsymmetricTestClass?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject(); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.RealmValue(AsymmetricTestClass? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.QueryArgument(AsymmetricTestClass? val) => (Realms.RealmValue)val; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this); + + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is InvalidObject) + { + return !IsValid; + } + + if (obj is not Realms.IRealmObjectBase iro) + { + return false; + } + + return Accessor.Equals(iro.Accessor); + } + + /// + public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode(); + + /// + public override string? ToString() => Accessor.ToString(); + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class AsymmetricTestClassObjectHelper : Realms.Weaving.IRealmObjectHelper + { + public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults) + { + throw new InvalidOperationException("This method should not be called for source generated classes."); + } + + public Realms.ManagedAccessor CreateAccessor() => new AsymmetricTestClassManagedAccessor(); + + public Realms.IRealmObjectBase CreateInstance() => new AsymmetricTestClass(); + + public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value) + { + value = RealmValue.Null; + return false; + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal interface IAsymmetricTestClassAccessor : Realms.IRealmAccessor + { + int Int32Property { get; set; } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class AsymmetricTestClassManagedAccessor : Realms.ManagedAccessor, IAsymmetricTestClassAccessor + { + public int Int32Property + { + get => (int)GetValue("Int32Property"); + set => SetValue("Int32Property", value); + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class AsymmetricTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IAsymmetricTestClassAccessor + { + public override ObjectSchema ObjectSchema => AsymmetricTestClass.RealmSchema; + + private int _int32Property; + public int Int32Property + { + get => _int32Property; + set + { + _int32Property = value; + RaisePropertyChanged("Int32Property"); + } + } + + public AsymmetricTestClassUnmanagedAccessor(Type objectType) : base(objectType) + { + } + + public override Realms.RealmValue GetValue(string propertyName) + { + return propertyName switch + { + "Int32Property" => _int32Property, + _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"), + }; + } + + public override void SetValue(string propertyName, Realms.RealmValue val) + { + switch (propertyName) + { + case "Int32Property": + Int32Property = (int)val; + return; + default: + throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}"); + } + } + + public override void SetValueUnique(string propertyName, Realms.RealmValue val) + { + throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique"); + } + + public override IList GetListValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"); + } + + public override ISet GetSetValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}"); + } + + public override IDictionary GetDictionaryValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); + } + } +} diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs new file mode 100644 index 0000000000..d194d39073 --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/EmbeddedTestClass_generated.cs @@ -0,0 +1,332 @@ +// +#nullable enable + +using Realms; +using Realms.Schema; +using Realms.Sync; +using Realms.Sync.ErrorHandling; +using Realms.Weaving; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml.Serialization; +using TestAsymmetricObject = Realms.IAsymmetricObject; +using TestEmbeddedObject = Realms.IEmbeddedObject; +using TestRealmObject = Realms.IRealmObject; + +[Generated] +[Woven(typeof(EmbeddedTestClassObjectHelper)), Realms.Preserve(AllMembers = true)] +public partial class EmbeddedTestClass : IEmbeddedObject, INotifyPropertyChanged, IReflectableType +{ + /// + /// Defines the schema for the class. + /// + public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("EmbeddedTestClass", ObjectSchema.ObjectType.EmbeddedObject) + { + Realms.Schema.Property.Primitive("Int32Property", Realms.RealmValueType.Int, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "Int32Property"), + }.Build(); + + #region IEmbeddedObject implementation + + private IEmbeddedTestClassAccessor? _accessor; + + Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; + + internal IEmbeddedTestClassAccessor Accessor => _accessor ??= new EmbeddedTestClassUnmanagedAccessor(typeof(EmbeddedTestClass)); + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsManaged => Accessor.IsManaged; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsValid => Accessor.IsValid; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsFrozen => Accessor.IsFrozen; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Realm? Realm => Accessor.Realm; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi; + + /// + [IgnoreDataMember, XmlIgnore] + public int BacklinksCount => Accessor.BacklinksCount; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.IRealmObjectBase? Parent => Accessor.GetParent(); + + void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults) + { + var newAccessor = (IEmbeddedTestClassAccessor)managedAccessor; + var oldAccessor = _accessor; + _accessor = newAccessor; + + if (helper != null && oldAccessor != null) + { + if (!skipDefaults || oldAccessor.Int32Property != default(int)) + { + newAccessor.Int32Property = oldAccessor.Int32Property; + } + } + + if (_propertyChanged != null) + { + SubscribeForNotifications(); + } + + OnManaged(); + } + + #endregion + + /// + /// Called when the object has been managed by a Realm. + /// + /// + /// This method will be called either when a managed object is materialized or when an unmanaged object has been + /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, + /// it is not yet clear whether the object is managed or not. + /// + partial void OnManaged(); + + private event PropertyChangedEventHandler? _propertyChanged; + + /// + public event PropertyChangedEventHandler? PropertyChanged + { + add + { + if (_propertyChanged == null) + { + SubscribeForNotifications(); + } + + _propertyChanged += value; + } + + remove + { + _propertyChanged -= value; + + if (_propertyChanged == null) + { + UnsubscribeFromNotifications(); + } + } + } + + /// + /// Called when a property has changed on this class. + /// + /// The name of the property. + /// + /// For this method to be called, you need to have first subscribed to . + /// This can be used to react to changes to the current object, e.g. raising for computed properties. + /// + /// + /// + /// class MyClass : IRealmObject + /// { + /// public int StatusCodeRaw { get; set; } + /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; + /// partial void OnPropertyChanged(string propertyName) + /// { + /// if (propertyName == nameof(StatusCodeRaw)) + /// { + /// RaisePropertyChanged(nameof(StatusCode)); + /// } + /// } + /// } + /// + /// Here, we have a computed property that depends on a persisted one. In order to notify any + /// subscribers that StatusCode has changed, we implement and + /// raise manually by calling . + /// + partial void OnPropertyChanged(string? propertyName); + + private void RaisePropertyChanged([CallerMemberName] string propertyName = "") + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + OnPropertyChanged(propertyName); + } + + private void SubscribeForNotifications() + { + Accessor.SubscribeForNotifications(RaisePropertyChanged); + } + + private void UnsubscribeFromNotifications() + { + Accessor.UnsubscribeFromNotifications(); + } + + /// + /// Converts a to . Equivalent to . + /// + /// The to convert. + /// The stored in the . + public static explicit operator EmbeddedTestClass?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject(); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.RealmValue(EmbeddedTestClass? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.QueryArgument(EmbeddedTestClass? val) => (Realms.RealmValue)val; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this); + + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is InvalidObject) + { + return !IsValid; + } + + if (obj is not Realms.IRealmObjectBase iro) + { + return false; + } + + return Accessor.Equals(iro.Accessor); + } + + /// + public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode(); + + /// + public override string? ToString() => Accessor.ToString(); + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class EmbeddedTestClassObjectHelper : Realms.Weaving.IRealmObjectHelper + { + public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults) + { + throw new InvalidOperationException("This method should not be called for source generated classes."); + } + + public Realms.ManagedAccessor CreateAccessor() => new EmbeddedTestClassManagedAccessor(); + + public Realms.IRealmObjectBase CreateInstance() => new EmbeddedTestClass(); + + public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value) + { + value = RealmValue.Null; + return false; + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal interface IEmbeddedTestClassAccessor : Realms.IRealmAccessor + { + int Int32Property { get; set; } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class EmbeddedTestClassManagedAccessor : Realms.ManagedAccessor, IEmbeddedTestClassAccessor + { + public int Int32Property + { + get => (int)GetValue("Int32Property"); + set => SetValue("Int32Property", value); + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class EmbeddedTestClassUnmanagedAccessor : Realms.UnmanagedAccessor, IEmbeddedTestClassAccessor + { + public override ObjectSchema ObjectSchema => EmbeddedTestClass.RealmSchema; + + private int _int32Property; + public int Int32Property + { + get => _int32Property; + set + { + _int32Property = value; + RaisePropertyChanged("Int32Property"); + } + } + + public EmbeddedTestClassUnmanagedAccessor(Type objectType) : base(objectType) + { + } + + public override Realms.RealmValue GetValue(string propertyName) + { + return propertyName switch + { + "Int32Property" => _int32Property, + _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"), + }; + } + + public override void SetValue(string propertyName, Realms.RealmValue val) + { + switch (propertyName) + { + case "Int32Property": + Int32Property = (int)val; + return; + default: + throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}"); + } + } + + public override void SetValueUnique(string propertyName, Realms.RealmValue val) + { + throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique"); + } + + public override IList GetListValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"); + } + + public override ISet GetSetValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}"); + } + + public override IDictionary GetDictionaryValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); + } + } +} diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs new file mode 100644 index 0000000000..7443657274 --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/JustForObjectReference_generated.cs @@ -0,0 +1,329 @@ +// +#nullable enable + +using Realms; +using Realms.Schema; +using Realms.Sync; +using Realms.Sync.ErrorHandling; +using Realms.Weaving; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml.Serialization; +using TestAsymmetricObject = Realms.IAsymmetricObject; +using TestEmbeddedObject = Realms.IEmbeddedObject; +using TestRealmObject = Realms.IRealmObject; + +[Generated] +[Woven(typeof(JustForObjectReferenceObjectHelper)), Realms.Preserve(AllMembers = true)] +public partial class JustForObjectReference : IRealmObject, INotifyPropertyChanged, IReflectableType +{ + /// + /// Defines the schema for the class. + /// + public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("JustForObjectReference", ObjectSchema.ObjectType.RealmObject) + { + Realms.Schema.Property.Object("UseAsBacklink", "RootRealmClass", managedName: "UseAsBacklink"), + }.Build(); + + #region IRealmObject implementation + + private IJustForObjectReferenceAccessor? _accessor; + + Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; + + internal IJustForObjectReferenceAccessor Accessor => _accessor ??= new JustForObjectReferenceUnmanagedAccessor(typeof(JustForObjectReference)); + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsManaged => Accessor.IsManaged; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsValid => Accessor.IsValid; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsFrozen => Accessor.IsFrozen; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Realm? Realm => Accessor.Realm; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi; + + /// + [IgnoreDataMember, XmlIgnore] + public int BacklinksCount => Accessor.BacklinksCount; + + void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults) + { + var newAccessor = (IJustForObjectReferenceAccessor)managedAccessor; + var oldAccessor = _accessor; + _accessor = newAccessor; + + if (helper != null && oldAccessor != null) + { + if (oldAccessor.UseAsBacklink != null && newAccessor.Realm != null) + { + newAccessor.Realm.Add(oldAccessor.UseAsBacklink, update); + } + newAccessor.UseAsBacklink = oldAccessor.UseAsBacklink; + } + + if (_propertyChanged != null) + { + SubscribeForNotifications(); + } + + OnManaged(); + } + + #endregion + + /// + /// Called when the object has been managed by a Realm. + /// + /// + /// This method will be called either when a managed object is materialized or when an unmanaged object has been + /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, + /// it is not yet clear whether the object is managed or not. + /// + partial void OnManaged(); + + private event PropertyChangedEventHandler? _propertyChanged; + + /// + public event PropertyChangedEventHandler? PropertyChanged + { + add + { + if (_propertyChanged == null) + { + SubscribeForNotifications(); + } + + _propertyChanged += value; + } + + remove + { + _propertyChanged -= value; + + if (_propertyChanged == null) + { + UnsubscribeFromNotifications(); + } + } + } + + /// + /// Called when a property has changed on this class. + /// + /// The name of the property. + /// + /// For this method to be called, you need to have first subscribed to . + /// This can be used to react to changes to the current object, e.g. raising for computed properties. + /// + /// + /// + /// class MyClass : IRealmObject + /// { + /// public int StatusCodeRaw { get; set; } + /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; + /// partial void OnPropertyChanged(string propertyName) + /// { + /// if (propertyName == nameof(StatusCodeRaw)) + /// { + /// RaisePropertyChanged(nameof(StatusCode)); + /// } + /// } + /// } + /// + /// Here, we have a computed property that depends on a persisted one. In order to notify any + /// subscribers that StatusCode has changed, we implement and + /// raise manually by calling . + /// + partial void OnPropertyChanged(string? propertyName); + + private void RaisePropertyChanged([CallerMemberName] string propertyName = "") + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + OnPropertyChanged(propertyName); + } + + private void SubscribeForNotifications() + { + Accessor.SubscribeForNotifications(RaisePropertyChanged); + } + + private void UnsubscribeFromNotifications() + { + Accessor.UnsubscribeFromNotifications(); + } + + /// + /// Converts a to . Equivalent to . + /// + /// The to convert. + /// The stored in the . + public static explicit operator JustForObjectReference?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject(); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.RealmValue(JustForObjectReference? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.QueryArgument(JustForObjectReference? val) => (Realms.RealmValue)val; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this); + + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is InvalidObject) + { + return !IsValid; + } + + if (obj is not Realms.IRealmObjectBase iro) + { + return false; + } + + return Accessor.Equals(iro.Accessor); + } + + /// + public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode(); + + /// + public override string? ToString() => Accessor.ToString(); + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class JustForObjectReferenceObjectHelper : Realms.Weaving.IRealmObjectHelper + { + public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults) + { + throw new InvalidOperationException("This method should not be called for source generated classes."); + } + + public Realms.ManagedAccessor CreateAccessor() => new JustForObjectReferenceManagedAccessor(); + + public Realms.IRealmObjectBase CreateInstance() => new JustForObjectReference(); + + public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value) + { + value = RealmValue.Null; + return false; + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal interface IJustForObjectReferenceAccessor : Realms.IRealmAccessor + { + RootRealmClass? UseAsBacklink { get; set; } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class JustForObjectReferenceManagedAccessor : Realms.ManagedAccessor, IJustForObjectReferenceAccessor + { + public RootRealmClass? UseAsBacklink + { + get => (RootRealmClass?)GetValue("UseAsBacklink"); + set => SetValue("UseAsBacklink", value); + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class JustForObjectReferenceUnmanagedAccessor : Realms.UnmanagedAccessor, IJustForObjectReferenceAccessor + { + public override ObjectSchema ObjectSchema => JustForObjectReference.RealmSchema; + + private RootRealmClass? _useAsBacklink; + public RootRealmClass? UseAsBacklink + { + get => _useAsBacklink; + set + { + _useAsBacklink = value; + RaisePropertyChanged("UseAsBacklink"); + } + } + + public JustForObjectReferenceUnmanagedAccessor(Type objectType) : base(objectType) + { + } + + public override Realms.RealmValue GetValue(string propertyName) + { + return propertyName switch + { + "UseAsBacklink" => _useAsBacklink, + _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"), + }; + } + + public override void SetValue(string propertyName, Realms.RealmValue val) + { + switch (propertyName) + { + case "UseAsBacklink": + UseAsBacklink = (RootRealmClass?)val; + return; + default: + throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}"); + } + } + + public override void SetValueUnique(string propertyName, Realms.RealmValue val) + { + throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique"); + } + + public override IList GetListValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"); + } + + public override ISet GetSetValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}"); + } + + public override IDictionary GetDictionaryValue(string propertyName) + { + throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); + } + } +} diff --git a/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs new file mode 100644 index 0000000000..3b15193961 --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/RootRealmClass_generated.cs @@ -0,0 +1,547 @@ +// +#nullable enable + +using Realms; +using Realms.Schema; +using Realms.Sync; +using Realms.Sync.ErrorHandling; +using Realms.Weaving; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using System.Xml.Serialization; +using TestAsymmetricObject = Realms.IAsymmetricObject; +using TestEmbeddedObject = Realms.IEmbeddedObject; +using TestRealmObject = Realms.IRealmObject; + +[Generated] +[Woven(typeof(RootRealmClassObjectHelper)), Realms.Preserve(AllMembers = true)] +public partial class RootRealmClass : IRealmObject, INotifyPropertyChanged, IReflectableType +{ + /// + /// Defines the schema for the class. + /// + public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("RootRealmClass", ObjectSchema.ObjectType.RealmObject) + { + Realms.Schema.Property.Object("JustForRef", "JustForObjectReference", managedName: "JustForRef"), + Realms.Schema.Property.ObjectList("ReferenceList", "JustForObjectReference", managedName: "ReferenceList"), + Realms.Schema.Property.PrimitiveList("PrimitiveList", Realms.RealmValueType.Int, areElementsNullable: false, managedName: "PrimitiveList"), + Realms.Schema.Property.ObjectDictionary("ReferenceDictionary", "JustForObjectReference", managedName: "ReferenceDictionary"), + Realms.Schema.Property.PrimitiveDictionary("PrimitiveDictionary", Realms.RealmValueType.Int, areElementsNullable: false, managedName: "PrimitiveDictionary"), + Realms.Schema.Property.ObjectSet("ReferenceSet", "JustForObjectReference", managedName: "ReferenceSet"), + Realms.Schema.Property.PrimitiveSet("PrimitiveSet", Realms.RealmValueType.Int, areElementsNullable: false, managedName: "PrimitiveSet"), + Realms.Schema.Property.Primitive("Counter", Realms.RealmValueType.Int, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "Counter"), + Realms.Schema.Property.RealmValue("RealmValue", managedName: "RealmValue"), + Realms.Schema.Property.Backlinks("JustBackLink", "JustForObjectReference", "UseAsBacklink", managedName: "JustBackLink"), + }.Build(); + + #region IRealmObject implementation + + private IRootRealmClassAccessor? _accessor; + + Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor; + + internal IRootRealmClassAccessor Accessor => _accessor ??= new RootRealmClassUnmanagedAccessor(typeof(RootRealmClass)); + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsManaged => Accessor.IsManaged; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsValid => Accessor.IsValid; + + /// + [IgnoreDataMember, XmlIgnore] + public bool IsFrozen => Accessor.IsFrozen; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Realm? Realm => Accessor.Realm; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!; + + /// + [IgnoreDataMember, XmlIgnore] + public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi; + + /// + [IgnoreDataMember, XmlIgnore] + public int BacklinksCount => Accessor.BacklinksCount; + + void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults) + { + var newAccessor = (IRootRealmClassAccessor)managedAccessor; + var oldAccessor = _accessor; + _accessor = newAccessor; + + if (helper != null && oldAccessor != null) + { + if (!skipDefaults) + { + newAccessor.ReferenceList.Clear(); + newAccessor.PrimitiveList.Clear(); + newAccessor.ReferenceDictionary.Clear(); + newAccessor.PrimitiveDictionary.Clear(); + newAccessor.ReferenceSet.Clear(); + newAccessor.PrimitiveSet.Clear(); + } + + if (oldAccessor.JustForRef != null && newAccessor.Realm != null) + { + newAccessor.Realm.Add(oldAccessor.JustForRef, update); + } + newAccessor.JustForRef = oldAccessor.JustForRef; + Realms.CollectionExtensions.PopulateCollection(oldAccessor.ReferenceList, newAccessor.ReferenceList, update, skipDefaults); + Realms.CollectionExtensions.PopulateCollection(oldAccessor.PrimitiveList, newAccessor.PrimitiveList, update, skipDefaults); + Realms.CollectionExtensions.PopulateCollection(oldAccessor.ReferenceDictionary, newAccessor.ReferenceDictionary, update, skipDefaults); + Realms.CollectionExtensions.PopulateCollection(oldAccessor.PrimitiveDictionary, newAccessor.PrimitiveDictionary, update, skipDefaults); + Realms.CollectionExtensions.PopulateCollection(oldAccessor.ReferenceSet, newAccessor.ReferenceSet, update, skipDefaults); + Realms.CollectionExtensions.PopulateCollection(oldAccessor.PrimitiveSet, newAccessor.PrimitiveSet, update, skipDefaults); + if (!skipDefaults || oldAccessor.Counter != default(Realms.RealmInteger)) + { + newAccessor.Counter = oldAccessor.Counter; + } + newAccessor.RealmValue = oldAccessor.RealmValue; + } + + if (_propertyChanged != null) + { + SubscribeForNotifications(); + } + + OnManaged(); + } + + #endregion + + /// + /// Called when the object has been managed by a Realm. + /// + /// + /// This method will be called either when a managed object is materialized or when an unmanaged object has been + /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked, + /// it is not yet clear whether the object is managed or not. + /// + partial void OnManaged(); + + private event PropertyChangedEventHandler? _propertyChanged; + + /// + public event PropertyChangedEventHandler? PropertyChanged + { + add + { + if (_propertyChanged == null) + { + SubscribeForNotifications(); + } + + _propertyChanged += value; + } + + remove + { + _propertyChanged -= value; + + if (_propertyChanged == null) + { + UnsubscribeFromNotifications(); + } + } + } + + /// + /// Called when a property has changed on this class. + /// + /// The name of the property. + /// + /// For this method to be called, you need to have first subscribed to . + /// This can be used to react to changes to the current object, e.g. raising for computed properties. + /// + /// + /// + /// class MyClass : IRealmObject + /// { + /// public int StatusCodeRaw { get; set; } + /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw; + /// partial void OnPropertyChanged(string propertyName) + /// { + /// if (propertyName == nameof(StatusCodeRaw)) + /// { + /// RaisePropertyChanged(nameof(StatusCode)); + /// } + /// } + /// } + /// + /// Here, we have a computed property that depends on a persisted one. In order to notify any + /// subscribers that StatusCode has changed, we implement and + /// raise manually by calling . + /// + partial void OnPropertyChanged(string? propertyName); + + private void RaisePropertyChanged([CallerMemberName] string propertyName = "") + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + OnPropertyChanged(propertyName); + } + + private void SubscribeForNotifications() + { + Accessor.SubscribeForNotifications(RaisePropertyChanged); + } + + private void UnsubscribeFromNotifications() + { + Accessor.UnsubscribeFromNotifications(); + } + + /// + /// Converts a to . Equivalent to . + /// + /// The to convert. + /// The stored in the . + public static explicit operator RootRealmClass?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject(); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.RealmValue(RootRealmClass? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val); + + /// + /// Implicitly constructs a from . + /// + /// The value to store in the . + /// A containing the supplied . + public static implicit operator Realms.QueryArgument(RootRealmClass? val) => (Realms.RealmValue)val; + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this); + + /// + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is InvalidObject) + { + return !IsValid; + } + + if (obj is not Realms.IRealmObjectBase iro) + { + return false; + } + + return Accessor.Equals(iro.Accessor); + } + + /// + public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode(); + + /// + public override string? ToString() => Accessor.ToString(); + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class RootRealmClassObjectHelper : Realms.Weaving.IRealmObjectHelper + { + public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults) + { + throw new InvalidOperationException("This method should not be called for source generated classes."); + } + + public Realms.ManagedAccessor CreateAccessor() => new RootRealmClassManagedAccessor(); + + public Realms.IRealmObjectBase CreateInstance() => new RootRealmClass(); + + public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value) + { + value = RealmValue.Null; + return false; + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal interface IRootRealmClassAccessor : Realms.IRealmAccessor + { + JustForObjectReference? JustForRef { get; set; } + + System.Collections.Generic.IList ReferenceList { get; } + + System.Collections.Generic.IList PrimitiveList { get; } + + System.Collections.Generic.IDictionary ReferenceDictionary { get; } + + System.Collections.Generic.IDictionary PrimitiveDictionary { get; } + + System.Collections.Generic.ISet ReferenceSet { get; } + + System.Collections.Generic.ISet PrimitiveSet { get; } + + Realms.RealmInteger Counter { get; set; } + + Realms.RealmValue RealmValue { get; set; } + + System.Linq.IQueryable JustBackLink { get; } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class RootRealmClassManagedAccessor : Realms.ManagedAccessor, IRootRealmClassAccessor + { + public JustForObjectReference? JustForRef + { + get => (JustForObjectReference?)GetValue("JustForRef"); + set => SetValue("JustForRef", value); + } + + private System.Collections.Generic.IList _referenceList = null!; + public System.Collections.Generic.IList ReferenceList + { + get + { + if (_referenceList == null) + { + _referenceList = GetListValue("ReferenceList"); + } + + return _referenceList; + } + } + + private System.Collections.Generic.IList _primitiveList = null!; + public System.Collections.Generic.IList PrimitiveList + { + get + { + if (_primitiveList == null) + { + _primitiveList = GetListValue("PrimitiveList"); + } + + return _primitiveList; + } + } + + private System.Collections.Generic.IDictionary _referenceDictionary = null!; + public System.Collections.Generic.IDictionary ReferenceDictionary + { + get + { + if (_referenceDictionary == null) + { + _referenceDictionary = GetDictionaryValue("ReferenceDictionary"); + } + + return _referenceDictionary; + } + } + + private System.Collections.Generic.IDictionary _primitiveDictionary = null!; + public System.Collections.Generic.IDictionary PrimitiveDictionary + { + get + { + if (_primitiveDictionary == null) + { + _primitiveDictionary = GetDictionaryValue("PrimitiveDictionary"); + } + + return _primitiveDictionary; + } + } + + private System.Collections.Generic.ISet _referenceSet = null!; + public System.Collections.Generic.ISet ReferenceSet + { + get + { + if (_referenceSet == null) + { + _referenceSet = GetSetValue("ReferenceSet"); + } + + return _referenceSet; + } + } + + private System.Collections.Generic.ISet _primitiveSet = null!; + public System.Collections.Generic.ISet PrimitiveSet + { + get + { + if (_primitiveSet == null) + { + _primitiveSet = GetSetValue("PrimitiveSet"); + } + + return _primitiveSet; + } + } + + public Realms.RealmInteger Counter + { + get => (Realms.RealmInteger)GetValue("Counter"); + set => SetValue("Counter", value); + } + + public Realms.RealmValue RealmValue + { + get => (Realms.RealmValue)GetValue("RealmValue"); + set => SetValue("RealmValue", value); + } + + private System.Linq.IQueryable _justBackLink = null!; + public System.Linq.IQueryable JustBackLink + { + get + { + if (_justBackLink == null) + { + _justBackLink = GetBacklinks("JustBackLink"); + } + + return _justBackLink; + } + } + } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + internal class RootRealmClassUnmanagedAccessor : Realms.UnmanagedAccessor, IRootRealmClassAccessor + { + public override ObjectSchema ObjectSchema => RootRealmClass.RealmSchema; + + private JustForObjectReference? _justForRef; + public JustForObjectReference? JustForRef + { + get => _justForRef; + set + { + _justForRef = value; + RaisePropertyChanged("JustForRef"); + } + } + + public System.Collections.Generic.IList ReferenceList { get; } = new List(); + + public System.Collections.Generic.IList PrimitiveList { get; } = new List(); + + public System.Collections.Generic.IDictionary ReferenceDictionary { get; } = new Dictionary(); + + public System.Collections.Generic.IDictionary PrimitiveDictionary { get; } = new Dictionary(); + + public System.Collections.Generic.ISet ReferenceSet { get; } = new HashSet(RealmSet.Comparer); + + public System.Collections.Generic.ISet PrimitiveSet { get; } = new HashSet(RealmSet.Comparer); + + private Realms.RealmInteger _counter; + public Realms.RealmInteger Counter + { + get => _counter; + set + { + _counter = value; + RaisePropertyChanged("Counter"); + } + } + + private Realms.RealmValue _realmValue; + public Realms.RealmValue RealmValue + { + get => _realmValue; + set + { + _realmValue = value; + RaisePropertyChanged("RealmValue"); + } + } + + public System.Linq.IQueryable JustBackLink => throw new NotSupportedException("Using backlinks is only possible for managed(persisted) objects."); + + public RootRealmClassUnmanagedAccessor(Type objectType) : base(objectType) + { + } + + public override Realms.RealmValue GetValue(string propertyName) + { + return propertyName switch + { + "JustForRef" => _justForRef, + "Counter" => _counter, + "RealmValue" => _realmValue, + "JustBackLink" => throw new NotSupportedException("Using backlinks is only possible for managed(persisted) objects."), + _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"), + }; + } + + public override void SetValue(string propertyName, Realms.RealmValue val) + { + switch (propertyName) + { + case "JustForRef": + JustForRef = (JustForObjectReference?)val; + return; + case "Counter": + Counter = (Realms.RealmInteger)val; + return; + case "RealmValue": + RealmValue = (Realms.RealmValue)val; + return; + default: + throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}"); + } + } + + public override void SetValueUnique(string propertyName, Realms.RealmValue val) + { + throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique"); + } + + public override IList GetListValue(string propertyName) + { + return propertyName switch + { + "ReferenceList" => (IList)ReferenceList, + "PrimitiveList" => (IList)PrimitiveList, + _ => throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"), + }; + } + + public override ISet GetSetValue(string propertyName) + { + return propertyName switch + { + "ReferenceSet" => (ISet)ReferenceSet, + "PrimitiveSet" => (ISet)PrimitiveSet, + _ => throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}"), + }; + } + + public override IDictionary GetDictionaryValue(string propertyName) + { + return propertyName switch + { + "ReferenceDictionary" => (IDictionary)ReferenceDictionary, + "PrimitiveDictionary" => (IDictionary)PrimitiveDictionary, + _ => throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"), + }; + } + } +} diff --git a/Tests/Weaver/AnalyticsAssembly/Program.cs b/Tests/Weaver/AnalyticsAssembly/Program.cs new file mode 100644 index 0000000000..ea925c19c4 --- /dev/null +++ b/Tests/Weaver/AnalyticsAssembly/Program.cs @@ -0,0 +1,366 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Realms; +using Realms.Sync; +using Realms.Sync.ErrorHandling; +#if TEST_WEAVER +using TestAsymmetricObject = Realms.AsymmetricObject; +using TestEmbeddedObject = Realms.EmbeddedObject; +using TestRealmObject = Realms.RealmObject; +#else +using TestAsymmetricObject = Realms.IAsymmetricObject; +using TestEmbeddedObject = Realms.IEmbeddedObject; +using TestRealmObject = Realms.IRealmObject; +#endif + +public class Program +{ +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + + public static void Main(string[] args) + { + + } + +#if GET_INSTANCE_ASYNC + public static async Task GetInstanceAsyncMethod() + { + _ = await Realm.GetInstanceAsync(); + } +#endif + +#if GET_INSTANCE + + public static void GetInstanceMethod() + { + _ = Realm.GetInstance(); + } +#endif + +#if FIND + public static void FindByPk() + { + _ = Realm.GetInstance().Find("aKey"); + } +#endif + +#if WRITE_ASYNC + public static async Task WriteAsyncMethod() + { + _ = Realm.GetInstance().WriteAsync(() => { }); + } +#endif + +#if THREAD_SAFE_REFERENCE + public static void ThreadSafeReferenceMethod() + { + _ = ThreadSafeReference.Create(new RootRealmClass()); + } +#endif + +#if SHOULD_COMPACT_ON_LAUNCH + public static void ShouldCompactOnLaunchMethod() + { + _ = new RealmConfiguration + { + ShouldCompactOnLaunch = (totalBytes, bytesUsed) => true + }; + } +#endif + +#if MIGRATION_CALLBACK + public static void MigrationCbMethod() + { + _ = new RealmConfiguration + { + MigrationCallback = (migration, oldSchemaVersion) => { } + }; + } +#endif + +#if REALM_CHANGED + public static void RealmChangedMethod() + { + Realm.GetInstance().RealmChanged += (sender, args) => { }; + } +#endif + +#if LIST_SUBSCRIBE_FOR_NOTIFICATIONS + public static void ListSubscribeForNotificationsMethod() + { + _ = new List().SubscribeForNotifications((sender, changes) => { }); + } +#endif + +#if SET_SUBSCRIBE_FOR_NOTIFICATIONS + public static void SetSubscribeForNotificationsMethod() + { + _ = new HashSet().SubscribeForNotifications((sender, changes) => { }); + } +#endif + +#if DICTIONARY_SUBSCRIBE_FOR_NOTIFICATIONS + public static void DictionarySubscribeForNotificationsMethod() + { + _ = new Dictionary().SubscribeForNotifications((sender, changes) => { }); + } +#endif + +#if RESULT_SUBSCRIBE_FOR_NOTIFICATIONS + public static void ResultSubscribeForNotificationsMethod() + { + _ = Enumerable.Empty().AsQueryable().SubscribeForNotifications((sender, changes) => { }); + } +#endif + +#if OBJECT_NOTIFICATION + public static void PropertyChangedMethod() + { + new RootRealmClass().PropertyChanged += (sender, e) => { }; + } +#endif + +#if ADD + public static void AddUpdateMethod() + { + Realm.GetInstance().Add(new RootRealmClass(), update: true); + } +#endif + +#if RECOVER_OR_DISCARD_UNSYNCED_CHANGES_HANDLER + public static void RecoverOrDiscardUnsyncedChangesHandlerMethod() + { + _ = new FlexibleSyncConfiguration(user: null) + { + ClientResetHandler = new RecoverOrDiscardUnsyncedChangesHandler() + }; + } +#endif + +#if RECOVER_UNSYNCED_CHANGES_HANDLER + public static void RecoverUnsyncedChangesHandlerMethod() + { + _ = new FlexibleSyncConfiguration(user: null) + { + ClientResetHandler = new RecoverUnsyncedChangesHandler() + }; + } +#endif + +#if DISCARD_UNSYNCED_CHANGES_HANDLER + public static void DiscardUnsyncedChangesHandlerMethod() + { + _ = new FlexibleSyncConfiguration(user: null) + { + ClientResetHandler = new DiscardUnsyncedChangesHandler() + }; + } +#endif + +#if MANUAL_RECOVERY_HANDLER + public static void ManualRecoveryHandlerMethod() + { + _ = new FlexibleSyncConfiguration(user: null) + { + ClientResetHandler = new ManualRecoveryHandler((clientResetException) => { }) + }; + } +#endif + +#if GET_PROGRESS_OBSERVABLE + public static void GetProgressObservableMethod() + { + _ = ((Session)new object()).GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely); + } +#endif + +#if PARTITION_SYNC_CONFIGURATION + public static void PartitionSyncConfigurationMethod() + { + _ = new PartitionSyncConfiguration("aPartition", user: null); + } +#endif + +#if FLEXIBLE_SYNC_CONFIGURATION + public static void FlexibleSyncConfigurationMethod() + { + _ = new FlexibleSyncConfiguration(user: null); + } +#endif + +#if ANONYMOUS + public static void AnonymousAuthenticationMethod() + { + _ = Credentials.Anonymous(); + } +#endif + +#if EMAIL_PASSWORD + public static void EmailPasswordAuthenticationMethod() + { + _ = Credentials.EmailPassword("email", "password"); + } +#endif + +#if FACEBOOK + public static void FacebookAuthenticationMethod() + { + _ = Credentials.Facebook("accessToken"); + } +#endif + +#if GOOGLE + public static void GoogleAuthenticationMethod() + { + _ = Credentials.Google("credential", GoogleCredentialType.IdToken); + } +#endif + +#if APPLE + public static void AppleAuthenticationMethod() + { + _ = Credentials.Apple("accessToken"); + } +#endif + +#if J_W_T + public static void JwtAuthenticationMethod() + { + _ = Credentials.JWT("customToken"); + } +#endif + +#if API_KEY + public static void ApiKeyAuthenticationMethod() + { + _ = Credentials.ApiKey("key"); + } +#endif + +#if SERVER_API_KEY + public static void ServerApiKeyAuthenticationMethod() + { + _ = Credentials.ServerApiKey("serverApiKey"); + } +#endif + +#if FUNCTION + public static void FunctionAuthenticationMethod() + { + _ = Credentials.Function(new object()); + } +#endif + +#if CALL_ASYNC + public static void CallAsyncMethod() + { + _ = ((User)new object()).Functions.CallAsync("functionName"); + } +#endif + +#if GET_MONGO_CLIENT + public static void GetMongoClientMethod() + { + _ = ((User)new object()).GetMongoClient("serviceName"); + } +#endif + +#if DYNAMIC_API + public static void DynamicApiMethod() + { + _ = Realm.GetInstance().DynamicApi; + } +#endif + +#if CONNECTION_NOTIFICATION + public static void ConnectionNotificationMethod() + { + Realm.GetInstance().SyncSession.PropertyChanged += (sender, args) => { }; + } +#endif + +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously +} + +#if I_EMBEDDED_OBJECT +public partial class EmbeddedTestClass : TestEmbeddedObject +{ + public int Int32Property { get; set; } +} +#endif + +public partial class JustForObjectReference : TestRealmObject +{ + public RootRealmClass UseAsBacklink { get; set; } +} + +public partial class RootRealmClass : TestRealmObject +{ + +#if REALM_OBJECT_REFERENCE + private JustForObjectReference JustForRef { get; set; } +#endif + +#if REFERENCE_LIST + private IList ReferenceList { get; } +#endif + +#if PRIMITIVE_LIST + private IList PrimitiveList { get; } +#endif + +#if REFERENCE_DICTIONARY + private IDictionary ReferenceDictionary { get; } +#endif + +#if PRIMITIVE_DICTIONARY + private IDictionary PrimitiveDictionary { get; } +#endif + +#if REFERENCE_SET + private ISet ReferenceSet { get; } +#endif + +#if PRIMITIVE_SET + private ISet PrimitiveSet { get; } +#endif + +#if REALM_INTEGER + private RealmInteger Counter { get; set; } +#endif + +#if REALM_VALUE + private RealmValue RealmValue { get; set; } +#endif + +#if BACKLINK_ATTRIBUTE + [Backlink(nameof(JustForObjectReference.UseAsBacklink))] + private IQueryable JustBackLink { get; } +#endif +} + +#if I_ASYMMETRIC_OBJECT +public partial class AsymmetricTestClass : TestAsymmetricObject +{ + public int Int32Property { get; set; } +} +#endif diff --git a/Tests/Weaver/AssemblyToProcess/TestSyncActive.cs b/Tests/Weaver/AssemblyToProcess/TestSyncActive.cs deleted file mode 100644 index 6df14aa4b5..0000000000 --- a/Tests/Weaver/AssemblyToProcess/TestSyncActive.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Realms.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -namespace AssemblyToProcess -{ - /** N.B. Automating the verification of this functionality would be lengthy, - * having a too little of a return for the effort put in. Hence, it was decided to test this feature manually. - * - * The easiest/fastest way to do this is: - * 1- place a breakpoint in ModuleWeaver.cs around line ~182, so to check the content of payload return from SubmitAnalytics() - * 2- run a whatever test and check that "Sync Enabled" is true - * 3- comment line ~26 in this file and rerun the test - * 4- check that "Sync Enabled" is now false - * - * If all went well, the functionality works as expected. However, if something isn't working as expected, - * maybe adding a conditional breakpoint in Analytics.SearchMethodOccurrence line ~170 - * with the following condition: - * type.FullName == "AssemblyToProcess.TestSyncActive" - * should easily help you spot what's wrong. - */ - - public class TestSyncActive - { - public void CreateSyncConfiguration() - { - var conf = new SyncConfiguration("randomText"); - } - } -} diff --git a/Tests/Weaver/Realm.FakeForWeaverTests/ChangeSet.cs b/Tests/Weaver/Realm.FakeForWeaverTests/ChangeSet.cs new file mode 100644 index 0000000000..6afbb06b39 --- /dev/null +++ b/Tests/Weaver/Realm.FakeForWeaverTests/ChangeSet.cs @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +namespace Realms +{ + public class ChangeSet + { + } +} diff --git a/Tests/Weaver/Realm.FakeForWeaverTests/Exceptions/RealmException.cs b/Tests/Weaver/Realm.FakeForWeaverTests/RealmException.cs similarity index 99% rename from Tests/Weaver/Realm.FakeForWeaverTests/Exceptions/RealmException.cs rename to Tests/Weaver/Realm.FakeForWeaverTests/RealmException.cs index bd77449b3e..981ec7e759 100644 --- a/Tests/Weaver/Realm.FakeForWeaverTests/Exceptions/RealmException.cs +++ b/Tests/Weaver/Realm.FakeForWeaverTests/RealmException.cs @@ -26,4 +26,4 @@ public RealmException(string message) : base(message) { } } -} \ No newline at end of file +} diff --git a/Tests/Weaver/Realm.Fody.Tests/AnalyticsTests.cs b/Tests/Weaver/Realm.Fody.Tests/AnalyticsTests.cs new file mode 100644 index 0000000000..93eec57379 --- /dev/null +++ b/Tests/Weaver/Realm.Fody.Tests/AnalyticsTests.cs @@ -0,0 +1,247 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using NUnit.Framework; +using RealmWeaver; + +namespace Analytics +{ + extern alias realm; + using Metric = realm::RealmWeaver.Metric; + + [TestFixture] + internal class Tests : WeaverTestBase + { + public static object[] Features = Metric.SdkFeatures.Keys.Select(f => new object[] { f, ConvertToUpperCase(f) }).ToArray(); + + private static readonly Lazy _frameworks = new(() => + { + var targetProject = Path.Combine(_analyticsAssemblyLocation!.Value, "AnalyticsAssembly.csproj"); + var doc = XDocument.Parse(File.ReadAllText(targetProject)); + return doc.Descendants("TargetFrameworks").Single().Value.Split(';'); + }); + + private static readonly Lazy _analyticsAssemblyLocation = new(() => + { + var folder = Directory.GetCurrentDirectory(); + while (folder != null && !Directory.GetFiles(folder, "Realm.sln").Any()) + { + folder = Path.GetDirectoryName(folder); + } + + return Path.Combine(folder!, "Tests", "Weaver", "AnalyticsAssembly"); + }); + + private static string ConvertToUpperCase(string target) + { + var strBuilder = new StringBuilder(); + strBuilder.Append(target[0]); + var charArray = target.ToCharArray(1, target.Length - 1); + foreach (var c in charArray) + { + if (char.IsUpper(c)) + { + strBuilder.Append('_'); + } + + strBuilder.Append(char.ToUpper(c)); + } + + return strBuilder.ToString(); + } + + [TestCaseSource(nameof(Features))] + public void ValidateFeatureUsage(string feature, string constant) + { + CompileAnalyticsProject(constant); + ValidateAnalyticsPayloadAllFrameworks(new[] { (featureName: Metric.SdkFeatures[feature], expectedValue: BsonValue.Create(1)) }); + } + + [Test] + public void Submit_WhenDisabled_PayloadIsEmpty() + { + CompileAnalyticsProject(); + + foreach (var framework in _frameworks.Value) + { + var response = WeaveRealm(framework, "Disabled"); + Assert.That(response, Is.EqualTo("Analytics disabled"), $"Analytics was not reported as disabled for framework {framework}."); + } + } + + [Test] + public void ValidateHostOS() + { + RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + CompileAnalyticsProject(); + ValidateAnalyticsPayloadAllFrameworks(new[] { + (featureName: Metric.Environment.HostOsType, expectedValue: BsonValue.Create(GetMetricsOS())), + (featureName: Metric.Environment.HostOsVersion, expectedValue: BsonValue.Create(Environment.OSVersion.Version.ToString())) }); + } + + [Test] + public void ValidateEnvironmentMetrics() + { + // This test validates that all environment metrics (Metric.Environment) are set. It doesn't validate + // the actual values, just that they exist in the payload. + CompileAnalyticsProject(); + foreach (var framework in _frameworks.Value) + { + var response = WeaveRealm(framework, "DryRun"); + var payload = BsonSerializer.Deserialize(response).AsBsonDocument; + + var environmentMetrics = typeof(Metric.Environment).GetFields(BindingFlags.Static | BindingFlags.Public) + .Select(f => (string)f.GetValue(null)!) + .ToArray(); + + foreach (var metric in environmentMetrics) + { + Assert.That(payload.Contains(metric), Is.True, $"Metric: {metric} not found in document: {response}"); + } + } + } + + [Test] + public void Metric_SdkFeatures_ContainsAllFeatureConstants() + { + var sdkFeatures = Metric.SdkFeatures; + var featureConstants = typeof(Metric.Feature).GetFields(BindingFlags.Static | BindingFlags.Public) + .Select(f => (string)f.GetValue(null)!) + .ToArray(); + + foreach (var constant in featureConstants) + { + Assert.That(sdkFeatures.ContainsKey(constant), Is.True, $"Constant: {constant} is not present as a key in Metric.SdkFeatures"); + } + } + + private static string GetMetricsOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Metric.OperatingSystem.Windows; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return Metric.OperatingSystem.MacOS; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return Metric.OperatingSystem.Linux; + } + + return Environment.OSVersion.Platform.ToString(); + } + + private void ValidateAnalyticsPayloadAllFrameworks((string featureName, BsonValue expectedValue)[] payloadFeatures) + { + foreach (var framework in _frameworks.Value) + { + var response = WeaveRealm(framework, "DryRun"); + var payload = BsonSerializer.Deserialize(response).AsBsonDocument; + + foreach (var (featureName, expectedValue) in payloadFeatures) + { + Assert.That(payload[featureName], Is.EqualTo(expectedValue), + $"For framework {framework}, field \"{expectedValue}\" doesn't match the expected value \"{expectedValue}\""); + } + } + } + + private string WeaveRealm(string framework, string collectionType) + { + try + { + var assemblyPath = Path.Combine(_analyticsAssemblyLocation.Value, "bin", "Release", framework, "AnalyticsAssembly.dll"); + + var payloadPath = Path.GetTempFileName(); + WeaveRealm(assemblyPath, XElement.Parse($"")); + + var counter = 0; + while (!File.Exists(payloadPath) || string.IsNullOrEmpty(File.ReadAllText(payloadPath))) + { + if (counter++ > 5000) + { + throw new Exception($"File at {payloadPath} did not appear after 5000 ms"); + } + + Task.Delay(1).Wait(); + } + + // Make sure the file has been written completely. + Task.Delay(10).Wait(); + + return File.ReadAllText(payloadPath); + } + catch (Exception ex) + { + Assert.Fail($"Exception while weaving: {ex.Message}"); + } + + return string.Empty; + } + + private static void CompileAnalyticsProject(params string[] constants) + { + var binPath = Path.Combine(_analyticsAssemblyLocation.Value, "bin"); + if (Directory.Exists(binPath)) + { + Directory.Delete(binPath, recursive: true); + } + + var targetProject = Path.Combine(_analyticsAssemblyLocation.Value, "AnalyticsAssembly.csproj"); + + RunCommand("dotnet", $"build {targetProject} -p:AnalyticsConstants={string.Join(";", constants)} --configuration=Release"); + } + + private static void RunCommand(string command, string arguments) + { + var process = new Process(); + process.StartInfo.FileName = command; + process.StartInfo.Arguments = arguments; + process.StartInfo.UseShellExecute = false; + + // this, together with b) is only for debugging + //process.StartInfo.RedirectStandardOutput = true; + //process.StartInfo.RedirectStandardError = true; + + process.Start(); + + // *** b) + //var output = process.StandardOutput.ReadToEnd(); + //Console.WriteLine(output); + //var err = process.StandardError.ReadToEnd(); + //Console.WriteLine(err);m + + process.WaitForExit(); + } + } +} diff --git a/Tests/Weaver/Realm.Fody.Tests/Realm.Fody.Tests.csproj b/Tests/Weaver/Realm.Fody.Tests/Realm.Fody.Tests.csproj index a965a92fbc..a25561b319 100644 --- a/Tests/Weaver/Realm.Fody.Tests/Realm.Fody.Tests.csproj +++ b/Tests/Weaver/Realm.Fody.Tests/Realm.Fody.Tests.csproj @@ -1,7 +1,8 @@  - net472;netcoreapp3.1 + net472;net6.0 + 9.0 true true false @@ -17,16 +18,12 @@ - - 8.0 - - - - - - + + + + false diff --git a/Tests/Weaver/Realm.Fody.Tests/WeaverTestBase.cs b/Tests/Weaver/Realm.Fody.Tests/WeaverTestBase.cs index 912f9f1140..ab13a0f458 100644 --- a/Tests/Weaver/Realm.Fody.Tests/WeaverTestBase.cs +++ b/Tests/Weaver/Realm.Fody.Tests/WeaverTestBase.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; using Fody; namespace RealmWeaver @@ -26,13 +27,17 @@ namespace RealmWeaver public abstract class WeaverTestBase { - protected readonly List _warnings = new List(); - protected readonly List _errors = new List(); - protected readonly List _messages = new List(); + protected readonly List _warnings = new(); + protected readonly List _errors = new(); + protected readonly List _messages = new(); - protected TestResult WeaveRealm(string assemblyPath) + protected TestResult WeaveRealm(string assemblyPath, XElement? config = null) { var weaver = new realm::ModuleWeaver(); + if (config != null) + { + weaver.Config = config; + } var result = weaver.ExecuteTestRun(assemblyPath, ignoreCodes: new[] { "80131869" }, runPeVerify: false); _warnings.AddRange(result.Warnings.Select(m => m.Text)); @@ -41,4 +46,4 @@ protected TestResult WeaveRealm(string assemblyPath) return result; } } -} \ No newline at end of file +} diff --git a/examples/QuickJournal/FodyWeavers.xml b/examples/QuickJournal/FodyWeavers.xml index cc07b89533..ef20bfeea8 100644 --- a/examples/QuickJournal/FodyWeavers.xml +++ b/examples/QuickJournal/FodyWeavers.xml @@ -1,3 +1,4 @@ - + \ No newline at end of file diff --git a/examples/SimpleToDoAvalonia/FodyWeavers.xml b/examples/SimpleToDoAvalonia/FodyWeavers.xml index cc07b89533..ef20bfeea8 100644 --- a/examples/SimpleToDoAvalonia/FodyWeavers.xml +++ b/examples/SimpleToDoAvalonia/FodyWeavers.xml @@ -1,3 +1,4 @@ - + \ No newline at end of file diff --git a/wrappers/realm-core b/wrappers/realm-core index 80d47a7c79..2cfb24b980 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 80d47a7c7959778ba50171466391b326dca3a444 +Subproject commit 2cfb24b980345d0518f8a5d8b4fa5a0440e9b3a9 diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp index 207a25ad49..fbc150ce7b 100644 --- a/wrappers/src/app_cs.cpp +++ b/wrappers/src/app_cs.cpp @@ -52,6 +52,7 @@ namespace realm { std::string s_platform_version; std::string s_device_name; std::string s_device_version; + std::string s_bundle_id; std::function s_user_callback; std::function s_void_callback; @@ -103,6 +104,7 @@ extern "C" { uint16_t* platform_version, size_t platform_version_len, uint16_t* device_name, size_t device_name_len, uint16_t* device_version, size_t device_version_len, + uint16_t* bundle_id, size_t bundle_id_len, UserCallbackT* user_callback, VoidCallbackT* void_callback, StringCallbackT* string_callback, @@ -114,6 +116,7 @@ extern "C" { s_platform_version = Utf16StringAccessor(platform_version, platform_version_len); s_device_name = Utf16StringAccessor(device_name, device_name_len); s_device_version = Utf16StringAccessor(device_version, device_version_len); + s_bundle_id = Utf16StringAccessor(bundle_id, bundle_id_len); s_user_callback = wrap_managed_callback(user_callback); s_void_callback = wrap_managed_callback(void_callback); @@ -136,6 +139,7 @@ extern "C" { config.device_info.platform_version = s_platform_version; config.device_info.device_name = s_device_name; config.device_info.device_version = s_device_version; + config.device_info.bundle_id = s_bundle_id; config.transport = std::make_shared(app_config.managed_http_client); config.base_url = Utf16StringAccessor(app_config.base_url, app_config.base_url_len).to_string();