diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index a95422795811..a7d82e382ade 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -44,6 +44,12 @@ The build system supports multiple platforms simultaneously:
The `configure` script is used to select which platforms to build. This script must be run before `make`.
+## MSBuild code
+
+Non-standard patterns in .targets files:
+
+- Always use `$(DeviceSpecificIntermediateOutputPath)` instead of `$(IntermediateOutputPath)`.
+
## Binding System
### bgen (Binding Generator)
@@ -331,4 +337,4 @@ try {
} catch (Exception e) {
// Code here
}
-```
\ No newline at end of file
+```
diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml
index 899777386f62..f9e8d7ccb9fa 100644
--- a/.github/workflows/linux-build.yml
+++ b/.github/workflows/linux-build.yml
@@ -2,6 +2,7 @@
name: Linux Build Verification
on:
pull_request:
+ workflow_dispatch:
permissions:
contents: read
diff --git a/Make.config b/Make.config
index 2974ed71547e..93b90db4453b 100644
--- a/Make.config
+++ b/Make.config
@@ -397,6 +397,15 @@ DOTNET_DIR=$(abspath $(TOP)/builds/downloads/$(DOTNET_INSTALL_NAME))
export DOTNET_ROOT=$(DOTNET_DIR)
# dotnet now is being looked up in the PATH
export PATH := $(DOTNET_DIR):$(PATH)
+
+# Disable build servers to prevent parallel make from hanging.
+# Build servers (MSBuild server, Roslyn/VBCSCompiler) inherit jobserver file
+# descriptors from make, and don't close them when daemonizing. This prevents
+# make from detecting that all jobs have finished, causing it to hang
+# indefinitely at the end of the build.
+export DOTNET_CLI_USE_MSBUILD_SERVER=0
+export UseSharedCompilation=false
+export MSBUILDDISABLENODEREUSE=1
DOTNET=$(DOTNET_DIR)/dotnet
DOTNET_BCL_DIR:=$(abspath $(TOP)/packages/microsoft.netcore.app.ref/$(DOTNET_BCL_VERSION)/ref/$(DOTNET_TFM))
# when bumping to a new .NET version, there may be a period when some parts of .NET is still on the old .NET version, so handle that here for DOTNET_BCL_DIR
diff --git a/Makefile b/Makefile
index 68482dab47b6..0dcd1bcd2fa1 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,8 @@ world: check-system
@$(MAKE) reset-versions
@$(MAKE) all -j8
@$(MAKE) install -j8
+ @echo "Build is done, the following workloads were built:"
+ @$(DOTNET) workload list
.PHONY: check-system
check-system:
@@ -68,9 +70,6 @@ install-hook::
exit 1; \
fi
-all-hook install-hook::
- $(Q) $(MAKE) -C dotnet shutdown-build-server
-
dotnet-install-system:
$(Q) $(MAKE) -C dotnet install-system
diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md
index 98623c7160e6..fa4d54b8e52e 100644
--- a/docs/building-apps/build-properties.md
+++ b/docs/building-apps/build-properties.md
@@ -582,6 +582,28 @@ See also:
* The [AlternateAppIcon](build-items.md#alternateappicon) item group.
* The [AppIcon](#appicon) property.
+## InlineDlfcnMethods
+
+Controls whether the build system replaces runtime calls to `ObjCRuntime.Dlfcn` methods with direct native symbol lookups at build time, eliminating the overhead of `dlsym` at runtime.
+
+The valid options are:
+
+* `compatibility`: Inlines dlfcn method calls but only creates native references for symbols used in `[Field]` attributes. This is more conservative and avoids link errors for symbols that don't exist at build time.
+* `strict`: Inlines dlfcn method calls and creates native references for all symbols. This is more aggressive and may cause link errors if referenced native symbols don't exist.
+* (empty): Disables inlining of dlfcn method calls.
+
+Default value:
+* .NET 11+: `strict` when using NativeAOT (`PublishAot=true`), `compatibility` otherwise.
+* .NET 10 and earlier: not set (disabled).
+
+Example:
+
+```xml
+
+ compatibility
+
+```
+
## iOSMinimumVersion
Specifies the minimum iOS version the app can run on.
diff --git a/docs/code/native-symbols.md b/docs/code/native-symbols.md
new file mode 100644
index 000000000000..8ff837ca2a41
--- /dev/null
+++ b/docs/code/native-symbols.md
@@ -0,0 +1,68 @@
+# Native symbols
+
+Native symbols can be referenced from managed code in several ways:
+
+* P/Invokes (DllImports)
+* Calls to `dlsym`, which can happen through:
+ * The various APIs in `ObjCRuntime.Dlfcn`
+ * The various APIs in `System.Runtime.InteropServices.NativeLibrary`
+ * A P/Invoke directly into `dlsym`
+
+It's highly desirable to use a direct native reference to native symbols when building a mobile app, for a few reasons:
+
+* It's faster at runtime, and the app is smaller.
+* If the referenced native symbol comes from a third-party static library, the
+ native linker can remove it if it's configured to remove unused code
+ (because the native linker can't see that the native symbol is in fact used
+ at runtime) unless there's a direct native reference to the symbol.
+
+On the other hand there's one scenario when a direct native reference is not desirable: when the native symbol does not exist.
+
+In order to create a direct native reference to native symbols, we need to know the names of those native symbols.
+
+## The `InlineDlfcnMethods` property
+
+This behavior is controlled by the `InlineDlfcnMethods` MSBuild property, which
+has two modes:
+
+* `strict`: all calls to `ObjCRuntime.Dlfcn` APIs are inlined.
+* `compatibility`: only calls that reference symbols from `[Field]` attributes are inlined.
+
+See the [build properties documentation](../building-apps/build-properties.md) for default values.
+
+## How it works
+
+During the build we try to collect the following:
+
+* Any property or field with the `[Foundation.Field]` attribute: we collect the symbol name.
+* Any calls to the `ObjCRuntime.Dlfcn` APIs: we try to collect the symbol name (this might not always succeed, if the symbol name is not a constant).
+* We don't process calls to `System.Runtime.InteropServices.NativeLibrary` at the moment (this may change in the future, if there's need).
+
+This is further complicated by the fact that we only want to create native
+references for symbols that survive trimming.
+
+So we do the following:
+
+1. During trimming, two custom linker steps execute:
+
+ * `InlineDlfcnMethodsStep`: for every symbol we've collected, this step
+ creates a P/Invoke to a native method that will return the address for
+ that symbol (using a direct native reference), and modifies the code
+ that fetches that symbol to call said P/Invoke.
+ * `GenerateInlinedDlfcnNativeCodeStep`: writes the complete list of
+ inlined symbols to a file (`inlined-dlfcn-symbols.txt`) for later
+ MSBuild targets to consume.
+
+2. After trimming, we figure out which of those symbols survived:
+
+ * For ILTrim: the `_CollectPostILTrimInformation` MSBuild target inspects
+ the trimmed assemblies and collects all the inlined dlfcn P/Invokes that
+ survived. Per-assembly results are cached to speed up incremental builds.
+ * For NativeAOT: the `_CollectPostNativeAOTTrimInformation` MSBuild target
+ inspects the native object file (or static library) produced by NativeAOT,
+ collects all unresolved native references, and filters them against the
+ inlined dlfcn symbols to determine which survived.
+
+3. The `_PostTrimmingProcessing` MSBuild target takes the surviving symbols
+ from either path, generates the corresponding native C code, and adds it to
+ the list of files to compile and link into the final executable.
diff --git a/dotnet/Makefile b/dotnet/Makefile
index a807fe5d4ccf..bd150b2f5e6c 100644
--- a/dotnet/Makefile
+++ b/dotnet/Makefile
@@ -537,23 +537,3 @@ clean-local::
$(Q) $(DOTNET) restore package/workaround-for-maccore-issue-2427/restore.csproj /bl:package/workaround-for-maccore-issue-2427/restore.binlog $(MSBUILD_VERBOSITY)
$(Q) touch $@
-# We need to shut down the builder server, because:
-# We're using parallel make, and parallel make will start a jobserver, managed by file descriptors, where these file descriptors must be closed in all subprocesses for make to realize it's done.
-# 'dotnet pack' might have started a build server
-# The build server does not close any file descriptors it may have inherited when daemonizing itself.
-# Thus the build server (which will still be alive after we're done building here) might have a file descriptor open which make is waiting for.
-# The proper fix is to fix the build server to close its file descriptors.
-# The intermediate working is to shut down the build server instead. An alternative solution would be to pass /p:UseSharedCompilation=false to 'dotnet pack' to disable the usage of the build server.
-#
-# The 'shutdown-build-server' is executed in a sub-make (and not as a dependency to the all-hook target),
-# to make sure it's executed after everything else is built in this file.
-all-hook::
- $(Q) $(MAKE) shutdown-build-server
-
-shutdown-build-server:
- $(Q) echo "Shutting down build servers:"
- $(Q) $(DOTNET) build-server shutdown | sed 's/^/ /' || true
- $(Q) echo "Listing .NET processes still alive:"
- $(Q) pgrep -lf "^$(DOTNET)" | sed 's/^/ /' || true
- $(Q) echo "Killing the above mentioned processes."
- $(Q) pkill -9 -f "^$(DOTNET)" | sed 's/^/ /' || true
diff --git a/dotnet/targets/Microsoft.Sdk.Desktop.targets b/dotnet/targets/Microsoft.Sdk.Desktop.targets
index 6b878200c30a..276822e3c2e4 100644
--- a/dotnet/targets/Microsoft.Sdk.Desktop.targets
+++ b/dotnet/targets/Microsoft.Sdk.Desktop.targets
@@ -3,8 +3,38 @@
+
+
+ <_DotNetWatchVariable Include="@(RuntimeEnvironmentVariable)" Condition="'%(Identity)' == 'DOTNET_WATCH' And '%(Value)' == '1'" />
+
+
+ <_IsDotNetWatch>true
+ true
+ true
+
+
+
+
+
+ <_TtyPath>@(_TtyOutput)
+ $(_TtyPath)
+ $(_TtyPath)
+
+
<_OpenArguments Condition="'$(XamarinDebugMode)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_MODE__=$(XamarinDebugMode)
<_OpenArguments Condition="'$(XamarinDebugPort)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_PORT__=$(XamarinDebugPort)
diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets
index 7ecf8141a073..db28965e02bd 100644
--- a/dotnet/targets/Microsoft.Sdk.Mobile.targets
+++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets
@@ -138,7 +138,7 @@
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets
index b4b62f461734..8c0de02f7174 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.targets
@@ -8,6 +8,9 @@
+
+
+
@@ -15,6 +18,13 @@
+
+
+
+
+ strict
+ compatibility
+
+
@@ -475,7 +487,16 @@
-
+
+
+
+
+ <_MonoLibrary Remove="@(_MonoRuntimeComponentDontLink -> '$(_MonoRuntimePackPathNative)%(Identity)')" />
+
+
+ <_MonoLibrary Include="@(_MonoRuntimeComponentLink -> '$(_MonoRuntimePackPathNative)%(Identity)')" />
+
+
<_ComputeLinkerArgumentsDependsOn>
@@ -626,7 +647,9 @@
@(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)')
@(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)')
Interpreter=$(MtouchInterpreter)
+ InlineDlfcnMethods=$(InlineDlfcnMethods)
IntermediateLinkDir=$(IntermediateLinkDir)
+ IntermediateOutputPath=$(DeviceSpecificIntermediateOutputPath)
InvariantGlobalization=$(InvariantGlobalization)
HybridGlobalization=$(HybridGlobalization)
ItemsDirectory=$(_LinkerItemsDirectory)
@@ -780,6 +803,7 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" />
+ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(InlineDlfcnMethods)' != ''" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Type="Xamarin.Linker.DoneStep" />
@@ -1613,6 +1638,81 @@
+
+
+ <_TrimmedAssembly Include="$(IntermediateLinkDir)*.dll" />
+
+
+
+
+
+ <_ILTrimSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)inlined-dlfcn\iltrim-surviving-native-symbols.txt
+ <_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt
+ <_NativeAOTSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-surviving-native-symbols.txt
+
+
+
+
+
+
+
+
+
+
+
+ <_SurvivingNativeSymbolsFile Include="$(_ILTrimSurvivingNativeSymbolsFile)" Condition="Exists('$(_ILTrimSurvivingNativeSymbolsFile)')" />
+ <_SurvivingNativeSymbolsFile Include="$(_NativeAOTSurvivingNativeSymbolsFile)" Condition="Exists('$(_NativeAOTSurvivingNativeSymbolsFile)')" />
+
+
+
+
+
+
+
+ <_PostTrimmingSourceFiles>
+ $(DeviceSpecificIntermediateOutputPath)posttrim-info-compiled/%(Filename).o
+
+
+
+
+
+
+
+ <_CompiledPostTrimmingFiles Include="@(_PostTrimmingSourceFiles -> '%(OutputFile)')" />
+ <_NativeExecutableObjectFiles Include="@(_CompiledPostTrimmingFiles)" />
+
+
+
+
<_CompileNativeExecutableDependsOn>
$(_CompileNativeExecutableDependsOn);
@@ -1669,6 +1769,7 @@
_ReadAppManifest;
_WriteAppManifest;
_CompileNativeExecutable;
+ _PostTrimmingProcessing;
_ReidentifyDynamicLibraries;
_AddSwiftLinkerFlags;
_ComputeLinkerProperties;
@@ -1677,6 +1778,26 @@
+
+
+
+
+
+
@@ -2585,6 +2706,28 @@ global using nfloat = global::System.Runtime.InteropServices.NFloat%3B
+
+
+
+ <_HotReloadVariable Include="@(RuntimeEnvironmentVariable)" Condition="'%(Identity)' == 'DOTNET_WATCH' And '%(Value)' == '1'" />
+
+
+ <_IsHotReloadLaunch>true
+
+
+
+
+
+
@@ -2609,10 +2752,19 @@ global using nfloat = global::System.Runtime.InteropServices.NFloat%3B
$(_R2RFrameworkName).framework.dSYM
+>>>>>>> origin/net11.0
+
+
+ <_CollectItemsForPostProcessingDependsOn>
+ _ComputeFrameworkFilesToPublish;
+ $(_CollectItemsForPostProcessingDependsOn);
+
+
+
<_ProjectLanguage>$(Language)
<_ProjectLanguage Condition="'$(_ProjectLanguage)' == '' Or '$(_ProjectLanguage)' == 'C#' ">CSharp
diff --git a/eng/Version.Details.props b/eng/Version.Details.props
index 140fd41caa14..45dd2b5f9b4c 100644
--- a/eng/Version.Details.props
+++ b/eng/Version.Details.props
@@ -26,7 +26,7 @@ This file should be imported by eng/Versions.props
26.0.1101726.2.10224
- 10.0.0-prerelease.25516.4
+ 11.0.0-prerelease.26166.1
diff --git a/macios/Localize/loc/es/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl b/macios/Localize/loc/es/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
index a798f5eb2587..e5e8b518fb75 100644
--- a/macios/Localize/loc/es/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
+++ b/macios/Localize/loc/es/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
@@ -1150,6 +1150,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/macios/Localize/loc/pt-BR/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl b/macios/Localize/loc/pt-BR/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
index a6c7018501a8..3921d8d0ddfb 100644
--- a/macios/Localize/loc/pt-BR/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
+++ b/macios/Localize/loc/pt-BR/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx.lcl
@@ -1150,6 +1150,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.cs.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.cs.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.cs.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.cs.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.de.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.de.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.de.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.de.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.es.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.es.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.es.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.es.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.fr.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.fr.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.fr.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.fr.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.it.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.it.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.it.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.it.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ja.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ja.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ja.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ja.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ko.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ko.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ko.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ko.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pl.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pl.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pl.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pl.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pt-BR.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pt-BR.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pt-BR.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.pt-BR.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ru.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ru.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ru.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.ru.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.tr.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.tr.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.tr.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.tr.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hans.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hans.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hans.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hans.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hant.resx b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hant.resx
index 75351409ba5c..5c22cfdd343a 100644
--- a/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hant.resx
+++ b/macios/src/rgen/Microsoft.Macios.Bindings.Analyzer/TranslatedAssemblies/Resources.zh-Hant.resx
@@ -473,4 +473,15 @@
Protocol constructor overlap:
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
\ No newline at end of file
diff --git a/msbuild/Xamarin.MacDev.Tasks/DotNetGlobals.cs b/msbuild/Xamarin.MacDev.Tasks/DotNetGlobals.cs
index 0afd19463727..8d530689d7b2 100644
--- a/msbuild/Xamarin.MacDev.Tasks/DotNetGlobals.cs
+++ b/msbuild/Xamarin.MacDev.Tasks/DotNetGlobals.cs
@@ -4,3 +4,5 @@
global using System;
global using System.Collections.Generic;
global using System.Runtime.InteropServices;
+
+global using Xamarin.Localization.MSBuild;
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs
new file mode 100644
index 000000000000..e97b7932721c
--- /dev/null
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs
@@ -0,0 +1,118 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using Microsoft.Build.Framework;
+
+using Mono.Cecil;
+
+#nullable enable
+
+namespace Xamarin.MacDev.Tasks {
+ ///
+ /// Scans trimmed assemblies to collect information that survived trimming.
+ /// Designed to be extensible for collecting additional types of information in the future.
+ /// See docs/code/native-symbols.md for an overview of native symbol handling.
+ ///
+ public class CollectPostILTrimInformation : XamarinTask {
+ [Required]
+ public ITaskItem [] TrimmedAssemblies { get; set; } = [];
+
+ ///
+ /// Output file listing the inlined dlfcn symbols that survived trimming.
+ ///
+ [Required]
+ public string SurvivingNativeSymbolsFile { get; set; } = "";
+
+ ///
+ /// Directory for per-assembly cache files, to avoid re-scanning unchanged assemblies.
+ ///
+ [Required]
+ public string CacheDirectory { get; set; } = "";
+
+ public override bool Execute ()
+ {
+ CollectSurvivingNativeSymbols ();
+ return !Log.HasLoggedErrors;
+ }
+
+ void CollectSurvivingNativeSymbols ()
+ {
+ Directory.CreateDirectory (CacheDirectory);
+
+ // Scan trimmed assemblies for surviving P/Invoke methods, using per-assembly caching.
+ var survivingSymbols = new HashSet ();
+ foreach (var item in TrimmedAssemblies) {
+ var assemblyPath = item.ItemSpec;
+ if (!File.Exists (assemblyPath))
+ continue;
+
+ var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
+ var cacheFile = Path.Combine (CacheDirectory, assemblyName + ".dlfcn-symbols.cache");
+
+ string []? cachedSymbols = null;
+ if (File.Exists (cacheFile) && File.GetLastWriteTimeUtc (cacheFile) >= File.GetLastWriteTimeUtc (assemblyPath)) {
+ cachedSymbols = File.ReadAllLines (cacheFile);
+ Log.LogMessage (MessageImportance.Low, "Using cached dlfcn symbols for {0}", assemblyName);
+ }
+
+ if (cachedSymbols is not null) {
+ foreach (var sym in cachedSymbols) {
+ if (sym.Length > 0)
+ survivingSymbols.Add (sym);
+ }
+ } else {
+ var assemblySymbols = new HashSet ();
+ CollectDlfcnSymbolsFromAssembly (assemblyPath, assemblySymbols);
+
+ // Write per-assembly cache (sorted for stability).
+ var sortedAssemblySymbols = assemblySymbols.OrderBy (s => s).ToArray ();
+ File.WriteAllLines (cacheFile, sortedAssemblySymbols);
+
+ foreach (var sym in assemblySymbols)
+ survivingSymbols.Add (sym);
+ }
+ }
+
+ // Write the combined results only if contents changed (sorted for stability).
+ var sorted = survivingSymbols.OrderBy (s => s).ToArray ();
+
+ if (File.Exists (SurvivingNativeSymbolsFile)) {
+ var existing = File.ReadAllLines (SurvivingNativeSymbolsFile);
+ if (existing.SequenceEqual (sorted))
+ return;
+ }
+
+ var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile);
+ if (!string.IsNullOrEmpty (dir))
+ Directory.CreateDirectory (dir);
+ File.WriteAllLines (SurvivingNativeSymbolsFile, sorted);
+ Log.LogMessage (MessageImportance.Low, "Found {0} surviving inlined dlfcn symbols", survivingSymbols.Count);
+ }
+
+ static void CollectDlfcnSymbolsFromAssembly (string assemblyPath, HashSet survivingSymbols)
+ {
+ const string prefix = "xamarin_Dlfcn_";
+ const string suffix = "_Native";
+
+ using var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, new ReaderParameters { ReadSymbols = false });
+ foreach (var module in assembly.Modules) {
+ foreach (var type in module.Types) {
+ if (!type.HasMethods)
+ continue;
+ foreach (var method in type.Methods) {
+ if (!method.IsPInvokeImpl)
+ continue;
+ if (method.PInvokeInfo?.Module?.Name != "__Internal")
+ continue;
+ var name = method.Name;
+ if (!name.StartsWith (prefix) || !name.EndsWith (suffix))
+ continue;
+ var symbolName = name.Substring (prefix.Length, name.Length - prefix.Length - suffix.Length);
+ survivingSymbols.Add (symbolName);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectUnresolvedNativeSymbols.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectUnresolvedNativeSymbols.cs
new file mode 100644
index 000000000000..c6543300fbaf
--- /dev/null
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectUnresolvedNativeSymbols.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using Microsoft.Build.Framework;
+
+#nullable enable
+
+namespace Xamarin.MacDev.Tasks {
+ // See docs/code/native-symbols.md for an overview of native symbol handling.
+ public class CollectUnresolvedNativeSymbols : XamarinTask {
+ [Required]
+ public ITaskItem StaticLibrary { get; set; } = null!;
+
+ [Required]
+ public string OutputFile { get; set; } = "";
+
+ public override bool Execute ()
+ {
+ var path = StaticLibrary.ItemSpec;
+ if (!File.Exists (path)) {
+ Log.LogError ("Static library not found: {0}", path);
+ return false;
+ }
+
+ var symbols = Xamarin.StaticLibrary.GetUnresolvedSymbols (path);
+ Log.LogMessage (MessageImportance.Low, "Found {0} unresolved symbols in {1}", symbols.Count, path);
+
+ var lines = symbols.OrderBy (s => s).ToArray ();
+ if (File.Exists (OutputFile)) {
+ var existing = File.ReadAllLines (OutputFile);
+ if (existing.SequenceEqual (lines))
+ return !Log.HasLoggedErrors;
+ }
+
+ var dir = Path.GetDirectoryName (OutputFile);
+ if (!string.IsNullOrEmpty (dir))
+ Directory.CreateDirectory (dir);
+ File.WriteAllLines (OutputFile, lines);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileNativeCode.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileNativeCode.cs
index affd9e1f3fae..f3816db30ddb 100644
--- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileNativeCode.cs
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CompileNativeCode.cs
@@ -136,6 +136,10 @@ public override bool Execute ()
arguments.Add ("-o");
arguments.Add (outputFile);
+ var outputDirectory = Path.GetDirectoryName (outputFile);
+ if (!string.IsNullOrEmpty (outputDirectory))
+ Directory.CreateDirectory (outputDirectory);
+
arguments.Add ("-c");
arguments.Add (src);
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeNativeAOTSurvivingNativeSymbols.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeNativeAOTSurvivingNativeSymbols.cs
new file mode 100644
index 000000000000..15282eb552f7
--- /dev/null
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeNativeAOTSurvivingNativeSymbols.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using Microsoft.Build.Framework;
+
+#nullable enable
+
+namespace Xamarin.MacDev.Tasks {
+ ///
+ /// Takes the list of unresolved native symbols from a NativeAOT static library and computes
+ /// which inlined dlfcn native symbols survived trimming. The output file has the same format
+ /// as CollectPostILTrimInformation's surviving symbols file.
+ /// See docs/code/native-symbols.md for an overview of native symbol handling.
+ ///
+ public class ComputeNativeAOTSurvivingNativeSymbols : XamarinTask {
+ ///
+ /// The file listing all unresolved native symbols from the NativeAOT static library.
+ ///
+ [Required]
+ public string UnresolvedSymbolsFile { get; set; } = "";
+
+ ///
+ /// The full list of inlined dlfcn symbols produced by the linker step.
+ ///
+ [Required]
+ public string InlinedDlfcnSymbolsFile { get; set; } = "";
+
+ ///
+ /// Output file listing the native symbols that survived NativeAOT trimming.
+ ///
+ [Required]
+ public string SurvivingNativeSymbolsFile { get; set; } = "";
+
+ public override bool Execute ()
+ {
+ if (!File.Exists (UnresolvedSymbolsFile) || !File.Exists (InlinedDlfcnSymbolsFile))
+ return !Log.HasLoggedErrors;
+
+ var allDlfcnSymbols = new HashSet (File.ReadAllLines (InlinedDlfcnSymbolsFile).Where (l => l.Length > 0));
+ if (allDlfcnSymbols.Count == 0)
+ return !Log.HasLoggedErrors;
+
+ const string prefix = "_xamarin_Dlfcn_";
+ const string suffix = "_Native";
+ var survivingSymbols = new HashSet ();
+
+ foreach (var sym in File.ReadAllLines (UnresolvedSymbolsFile).Where (l => l.Length > 0)) {
+ if (!sym.StartsWith (prefix) || !sym.EndsWith (suffix))
+ continue;
+ var symbolName = sym.Substring (prefix.Length, sym.Length - prefix.Length - suffix.Length);
+ if (allDlfcnSymbols.Contains (symbolName))
+ survivingSymbols.Add (symbolName);
+ }
+
+ var sorted = survivingSymbols.OrderBy (s => s).ToArray ();
+
+ if (File.Exists (SurvivingNativeSymbolsFile)) {
+ var existing = File.ReadAllLines (SurvivingNativeSymbolsFile);
+ if (existing.SequenceEqual (sorted))
+ return !Log.HasLoggedErrors;
+ }
+
+ var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile);
+ if (!string.IsNullOrEmpty (dir))
+ Directory.CreateDirectory (dir);
+ File.WriteAllLines (SurvivingNativeSymbolsFile, sorted);
+ Log.LogMessage (MessageImportance.Low, "Found {0} surviving native symbols from NativeAOT (out of {1} inlined dlfcn symbols)", survivingSymbols.Count, allDlfcnSymbols.Count);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs
new file mode 100644
index 000000000000..4856d3785c4f
--- /dev/null
+++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+using Microsoft.Build.Framework;
+
+#nullable enable
+
+namespace Xamarin.MacDev.Tasks {
+ ///
+ /// Performs post-trimming processing, generating native code only for symbols that survived trimming.
+ /// See docs/code/native-symbols.md for an overview of native symbol handling.
+ ///
+ public class PostTrimmingProcessing : XamarinTask {
+ ///
+ /// Files listing native symbols that survived trimming. Each file contains one symbol name per line.
+ /// These can come from either ILTrim (CollectPostILTrimInformation) or NativeAOT
+ /// (ComputeNativeAOTSurvivingNativeSymbols).
+ ///
+ public ITaskItem [] SurvivingNativeSymbolsFiles { get; set; } = [];
+
+ [Required]
+ public string OutputDirectory { get; set; } = "";
+
+ [Required]
+ public string Architecture { get; set; } = "";
+
+ ///
+ /// Output native source files to be compiled and linked.
+ ///
+ [Output]
+ public ITaskItem []? NativeSourceFiles { get; set; }
+
+ public override bool Execute ()
+ {
+ var items = new List ();
+
+ GenerateInlinedDlfcnNativeCode (items);
+
+ NativeSourceFiles = items.ToArray ();
+ return !Log.HasLoggedErrors;
+ }
+
+ void GenerateInlinedDlfcnNativeCode (List items)
+ {
+ // Collect all surviving symbols from all input files.
+ var survivingSymbols = new HashSet ();
+ foreach (var file in SurvivingNativeSymbolsFiles) {
+ var path = file.ItemSpec;
+ if (!File.Exists (path))
+ continue;
+ foreach (var line in File.ReadAllLines (path)) {
+ if (line.Length > 0)
+ survivingSymbols.Add (line);
+ }
+ }
+
+ if (survivingSymbols.Count == 0)
+ return;
+
+ Directory.CreateDirectory (OutputDirectory);
+ var outputPath = Path.Combine (OutputDirectory, "inlined-dlfcn.c");
+
+ var sb = new StringBuilder ();
+ foreach (var field in survivingSymbols.OrderBy (s => s)) {
+ sb.AppendLine ($"extern void* {field};");
+ sb.AppendLine ($"void* xamarin_Dlfcn_{field}_Native ();");
+ sb.AppendLine ($"void* xamarin_Dlfcn_{field}_Native () {{ return &{field}; }}");
+ sb.AppendLine ();
+ }
+
+ var content = sb.ToString ();
+ if (File.Exists (outputPath) && File.ReadAllText (outputPath) == content) {
+ Log.LogMessage (MessageImportance.Low, "Inlined dlfcn native code is up to date");
+ } else {
+ File.WriteAllText (outputPath, content);
+ Log.LogMessage (MessageImportance.Low, "Generated inlined dlfcn native code with {0} symbols", survivingSymbols.Count);
+ }
+
+ var item = new Microsoft.Build.Utilities.TaskItem (outputPath);
+ item.SetMetadata ("Arch", Architecture.ToLowerInvariant ());
+ items.Add (item);
+ }
+ }
+}
diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj
index 212e46bab7a8..899dfdeb2160 100644
--- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj
+++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj
@@ -140,66 +140,66 @@
ResxResXFileCodeGeneratorErrors.designer.cs
- Xamarin.Bundler
- Xamarin.Bundler.Errors
+ Xamarin.Localization.MSBuild
+ Xamarin.Localization.MSBuild.ErrorsErrors.designer.csCSharp
- Xamarin.Bundler
+ Xamarin.Localization.MSBuildErrorstruetrue
Errors.cs.resx
- Xamarin.Bundler.Errors.cs
+ Xamarin.Localization.MSBuild.Errors.cs
Errors.de.resx
- Xamarin.Bundler.Errors.de
+ Xamarin.Localization.MSBuild.Errors.de
Errors.es.resx
- Xamarin.Bundler.Errors.es
+ Xamarin.Localization.MSBuild.Errors.es
Errors.fr.resx
- Xamarin.Bundler.Errors.fr
+ Xamarin.Localization.MSBuild.Errors.fr
Errors.it.resx
- Xamarin.Bundler.Errors.it
+ Xamarin.Localization.MSBuild.Errors.it
Errors.ja.resx
- Xamarin.Bundler.Errors.ja
+ Xamarin.Localization.MSBuild.Errors.ja
Errors.ko.resx
- Xamarin.Bundler.Errors.ko
+ Xamarin.Localization.MSBuild.Errors.ko
Errors.pl.resx
- Xamarin.Bundler.Errors.pl
+ Xamarin.Localization.MSBuild.Errors.pl
Errors.pt-BR.resx
- Xamarin.Bundler.Errors.pt-BR
+ Xamarin.Localization.MSBuild.Errors.pt-BR
Errors.ru.resx
- Xamarin.Bundler.Errors.ru
+ Xamarin.Localization.MSBuild.Errors.ru
Errors.tr.resx
- Xamarin.Bundler.Errors.tr
+ Xamarin.Localization.MSBuild.Errors.tr
Errors.zh-Hans.resx
- Xamarin.Bundler.Errors.zh-Hans
+ Xamarin.Localization.MSBuild.Errors.zh-Hans
Errors.zh-Hant.resx
- Xamarin.Bundler.Errors.zh-Hant
+ Xamarin.Localization.MSBuild.Errors.zh-Hant
diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
index 957d57eece01..6cee261f5b67 100644
--- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets
+++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets
@@ -2950,7 +2950,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
- <_PostProcessingItem Include="@(_FrameworkNativeReference->'$(_AppBundleName)$(AppBundleExtension)/$(_AppFrameworksRelativePath)%(Filename)%(Extension).framework/%(Filename)%(Extension)')" Condition="'%(Kind)' == 'Framework'">
+
+ <_PostProcessingItem Include="@(_FilteredFrameworkToPublish->'$(_AppBundleName)$(AppBundleExtension)/$(_AppFrameworksRelativePath)%(Filename)%(Extension).framework/%(Filename)%(Extension)')">
%(Identity)
@@ -2959,7 +2961,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
%(Filename)%(Extension).framework.dSYM
- <_PostProcessingItem Include="@(_FileNativeReference->'$(_AppBundleName)$(AppBundleExtension)/$(_AppContentsRelativePathForPostProcessing)%(Filename)%(Extension)')" Condition="'%(Kind)' == 'Dynamic'">
+ <_PostProcessingItem Include="@(_FileNativeReference->'$(_AppBundleName)$(AppBundleExtension)/$(_AppContentsRelativePathForPostProcessing)%(Filename)%(Extension)')" Condition="'%(_FileNativeReference.Kind)' == 'Dynamic'">
%(Identity)
diff --git a/runtime/monovm-bridge.m b/runtime/monovm-bridge.m
index 6013963693da..faa1c81967cf 100644
--- a/runtime/monovm-bridge.m
+++ b/runtime/monovm-bridge.m
@@ -170,9 +170,9 @@
}
MonoClass *
-xamarin_get_nsobject_class ()
+xamarin_get_nsobject_class (bool allowAbsence)
{
- if (nsobject_class == NULL)
+ if (nsobject_class == NULL && !allowAbsence)
xamarin_assertion_message ("Internal consistency error, please file a bug (https://github.com/dotnet/macios/issues/new). Additional data: can't get the %s class because it's been linked away.\n", "NSObject");
return nsobject_class;
}
@@ -225,7 +225,7 @@
bool
xamarin_is_class_nsobject (MonoClass *cls)
{
- return mono_class_is_subclass_of (cls, xamarin_get_nsobject_class (), false);
+ return mono_class_is_subclass_of (cls, xamarin_get_nsobject_class (false), false);
}
bool
diff --git a/runtime/runtime.m b/runtime/runtime.m
index ab49902283b0..41a5d73f083b 100644
--- a/runtime/runtime.m
+++ b/runtime/runtime.m
@@ -901,7 +901,7 @@ -(struct NSObjectData*) xamarinGetNSObjectData;
{
gboolean rv = false;
- MonoClass *nsobject_class = xamarin_get_nsobject_class ();
+ MonoClass *nsobject_class = xamarin_get_nsobject_class (true);
if (nsobject_class)
rv = cls == nsobject_class || mono_class_is_assignable_from (nsobject_class, cls);
diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h
index eebdd575d66a..5323ffe95a51 100644
--- a/runtime/xamarin/runtime.h
+++ b/runtime/xamarin/runtime.h
@@ -279,7 +279,7 @@ MonoType * xamarin_get_nsnumber_type ();
MonoType * xamarin_get_nsvalue_type ();
MonoClass * xamarin_get_inativeobject_class ();
MonoClass * xamarin_get_nativehandle_class ();
-MonoClass * xamarin_get_nsobject_class ();
+MonoClass * xamarin_get_nsobject_class (bool allowAbsence);
MonoClass * xamarin_get_nsstring_class ();
MonoClass * xamarin_get_runtime_class ();
diff --git a/src/CoreAnimation/CAKeyFrameAnimation.cs b/src/CoreAnimation/CAKeyFrameAnimation.cs
index cdeaa54a1f8b..751cc9ebaff8 100644
--- a/src/CoreAnimation/CAKeyFrameAnimation.cs
+++ b/src/CoreAnimation/CAKeyFrameAnimation.cs
@@ -5,14 +5,9 @@
namespace CoreAnimation {
public partial class CAKeyFrameAnimation {
- /// Generic type to get teh values as.
- /// Returns the elements of the key frame animation as an
- /// array of strongly typed values of NSObject or CoreGraphics objects.
- ///
- ///
- ///
- ///
- public T [] GetValuesAs () where T : class, INativeObject
+ /// Returns the elements of the key frame animation as an array of strongly typed values of or CoreGraphics objects.
+ /// Generic type to get the values as.
+ public T []? GetValuesAs () where T : class, INativeObject
{
return NSArray.FromArrayNative (_Values);
}
diff --git a/src/CoreGraphics/CGFont.cs b/src/CoreGraphics/CGFont.cs
index 71ab4c33e784..81294a908892 100644
--- a/src/CoreGraphics/CGFont.cs
+++ b/src/CoreGraphics/CGFont.cs
@@ -125,7 +125,7 @@ protected internal override void Release ()
// and have a unit tests to make sure this behavior does not change over time
if (name is null)
return null;
- var nameHandle = new TransientCFString (name);
+ using var nameHandle = new TransientCFString (name);
return Create (CGFontCreateWithFontName (nameHandle));
}
diff --git a/src/Foundation/DictionaryContainer.cs b/src/Foundation/DictionaryContainer.cs
index 76b82f406d5e..2ded6c0e7825 100644
--- a/src/Foundation/DictionaryContainer.cs
+++ b/src/Foundation/DictionaryContainer.cs
@@ -215,7 +215,7 @@ bool TryGetNativeValue (NativeHandle key, out NativeHandle value)
if (!TryGetNativeValue (key, out var value))
return null;
- return NSArray.ArrayFromHandleFunc (value, creator);
+ return NSArray.ArrayFromHandleDropNullElements (value, (v) => creator (v), NSNullBehavior.DropIfIncompatible);
}
/// Retrieves the array associeted with .
@@ -226,7 +226,7 @@ bool TryGetNativeValue (NativeHandle key, out NativeHandle value)
if (!TryGetNativeValue (key, out var value))
return null;
- return NSArray.ArrayFromHandleFunc (value, (handle) => Create (handle)!);
+ return NSArray.ArrayFromHandleDropNullElements (value, (handle) => Create (handle)!, NSNullBehavior.DropIfIncompatible);
}
/// Returns the nullable associated with the specified .
diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs
index e10f6c2f9fe3..4ddefa458b02 100644
--- a/src/Foundation/NSArray.cs
+++ b/src/Foundation/NSArray.cs
@@ -686,6 +686,18 @@ static bool TryGetItem (NativeHandle elementHandle, Converter (handle, (h) => Runtime.GetINativeObject (h, false)!, nsNullElementBehavior, releaseHandle)!;
}
+ /// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements.
+ /// Parameter type, determines the kind of array returned.
+ /// Pointer (handle) to the unmanaged object.
+ /// A delegate to convert a native handle to an object of type T.
+ /// How to handle null and NSNull elements in the native array.
+ /// Whether the native NSArray instance should be released before returning or not.
+ /// A C# array with the values (excluding null elements). Returns if the handle is .
+ internal static T []? ArrayFromHandleDropNullElements (NativeHandle handle, Converter createObject, NSNullBehavior nsNullElementBehavior, bool releaseHandle = false)
+ {
+ return ArrayFromHandle (handle, createObject, nsNullElementBehavior, releaseHandle)!;
+ }
+
/// Returns a strongly-typed C# array from a handle to an NSArray, dropping null elements and guaranteeing a non-null return value.
/// Parameter type, determines the kind of array returned.
/// Pointer (handle) to the unmanaged object.
@@ -806,68 +818,64 @@ internal static T [] ToNonNullArrayDropNullElements (NSArray? weakArray) wher
GC.KeepAlive (weakArray);
return rv;
}
-#nullable disable
- /// Parameter type, determines the kind of
- /// array returned, can be either an NSObject, or other
- /// CoreGraphics data types.
- /// Handle to an weakly typed NSArray.
- /// Returns a strongly-typed C# array of the parametrized type from a weakly typed NSArray.
- /// An C# array with the values.
- ///
- /// Use this method to get a set of NSObject arrays from an NSArray.
- ///
- /// Returns a strongly-typed C# array of the parametrized type from a weakly typed NSArray.
+ /// Parameter type, determines the kind of array returned, can be either an , or other CoreGraphics data types.
+ /// Handle to a weakly typed NSArray.
+ /// A C# array with the values.
+ ///
+ /// Use this method to get a set of NSObject arrays from an NSArray.
+ ///
+ /// (someArray);
+ /// var myImages = NSArray.FromArrayNative (someArray);
/// ]]>
- ///
- ///
- static public T [] FromArrayNative (NSArray weakArray) where T : class, INativeObject
+ ///
+ ///
+ public static T []? FromArrayNative (NSArray? weakArray) where T : class, INativeObject
{
- if (weakArray is null || weakArray.Handle == NativeHandle.Zero)
- return null;
try {
- nuint n = weakArray.Count;
- T [] ret = new T [n];
- for (nuint i = 0; i < n; i++) {
- ret [i] = Runtime.GetINativeObject (weakArray.ValueAt (i), false);
- }
- return ret;
+ var rv = ArrayFromHandleDropNullElements (weakArray.GetHandle (), NSNullBehavior.DropIfIncompatible);
+ GC.KeepAlive (weakArray);
+ return rv;
} catch {
return null;
}
}
- // Used when we need to provide our constructor
- /// Parameter type, determines the kind of array returned.
- /// Pointer (handle) to the unmanaged object.
- /// To be added.
- /// Returns a strongly-typed C# array of the parametrized type from a handle to an NSArray.
- /// An C# array with the values.
+ /// Creates a strongly-typed C# array from a handle to an , using a custom factory function.
+ /// The element type for the returned array.
+ /// Pointer (handle) to the unmanaged object.
+ /// A factory function that creates an instance of from a native handle.
+ /// A C# array with the values, or if is .
///
- /// Use this method to get a set of NSObject arrays from a handle to an NSArray. Instead of wrapping the results in NSObjects, the code invokes your method to create the return value.
- ///
- /// (someHandle, (x) => (int) x);
+ ///
+ /// Instead of wrapping the results in instances,
+ /// this method invokes for each element to create the return value.
+ ///
+ ///
+ /// (someHandle, (x) => (int) x);
/// ]]>
- ///
- ///
- static public T [] ArrayFromHandleFunc (NativeHandle handle, Func createObject)
+ ///
+ ///
+ public static T? []? ArrayFromHandleFunc (NativeHandle handle, Func createObject)
{
return ArrayFromHandle (handle, (v) => createObject (v));
}
- /// Create a managed array from a pointer to a native NSArray instance.
- /// The pointer to the native NSArray instance.
- /// A callback that returns an instance of the type T for a given pointer (for an element in the NSArray).
- /// Whether the native NSArray instance should be released before returning or not.
- public static T [] ArrayFromHandleFunc (NativeHandle handle, Func createObject, bool releaseHandle)
+ /// Creates a strongly-typed C# array from a handle to an , using a custom factory function.
+ /// The element type for the returned array.
+ /// Pointer (handle) to the unmanaged object.
+ /// A factory function that creates an instance of from a native handle.
+ /// Whether the native instance should be released before returning or not.
+ /// A C# array with the values, or if is .
+ public static T? []? ArrayFromHandleFunc (NativeHandle handle, Func createObject, bool releaseHandle)
{
return ArrayFromHandle (handle, (v) => createObject (v), releaseHandle);
}
-#nullable enable
/// Creates a managed array from a pointer to a native NSArray of NSDictionary objects, dropping null and NSNull elements.
/// The type of objects to create from the dictionaries.
/// The pointer to the native NSArray instance containing NSDictionary objects.
@@ -926,21 +934,20 @@ internal static T [] NonNullDictionaryArrayFromHandleDropNullElements (Native
return Runtime.GetINativeObject (val, false);
}
-#nullable disable
- // can return an INativeObject or an NSObject
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
- public T GetItem (nuint index) where T : class, INativeObject
+ /// Returns the element at the specified index in the , as a strongly-typed object.
+ /// The type to return the element as. Must be a class that implements .
+ /// The zero-based index of the element to retrieve.
+ /// The element at , or if the element cannot be converted to .
+ /// is greater than or equal to the array's count.
+ public T? GetItem (nuint index) where T : class, INativeObject
{
if (index >= GetCount (Handle))
- throw new ArgumentOutOfRangeException ("index");
+ throw new ArgumentOutOfRangeException (nameof (index));
return UnsafeGetItem (Handle, index);
}
+#nullable disable
/// To be added.
/// To be added.
/// To be added.
@@ -980,6 +987,41 @@ public static NSArray From (NSObject [] [] items)
}
}
+#nullable enable
+ /// Converts this to a strongly-typed C# array, dropping null and incompatible elements.
+ /// The element type for the returned array. Must be a class that implements .
+ /// A C# array of elements, excluding any null or incompatible elements.
+ internal T []? ToArrayDropNullElements () where T : class, INativeObject
+ {
+ var rv = ArrayFromHandleDropNullElements (Handle);
+ GC.KeepAlive (this);
+ return rv;
+ }
+
+ /// Converts this to a strongly-typed C# array using a custom converter, dropping null elements.
+ /// The element type for the returned array.
+ /// A delegate to convert a native handle to an instance of .
+ /// A C# array of elements, excluding any null elements.
+ internal T []? ToArrayDropNullElements (Converter createObject)
+ {
+ var rv = ArrayFromHandleDropNullElements (Handle, createObject);
+ GC.KeepAlive (this);
+ return rv;
+ }
+
+ /// Converts this to a C# array by first resolving each element to , then converting to , dropping null elements.
+ /// The target element type for the returned array.
+ /// The intermediate native object type used to convert each element. Must be a class that implements .
+ /// A delegate to convert an instance of to .
+ /// A C# array of elements, excluding any null elements.
+ internal T []? ToArrayDropNullElements (Converter createObject) where V : class, INativeObject
+ {
+ var rv = ArrayFromHandleDropNullElements (Handle, (handle) => createObject (Runtime.GetINativeObject (handle, false)!));
+ GC.KeepAlive (this);
+ return rv;
+ }
+#nullable disable
+
public TKey [] ToArray () where TKey : class, INativeObject
{
var rv = new TKey [GetCount (Handle)];
diff --git a/src/Foundation/NSArray_1.cs b/src/Foundation/NSArray_1.cs
index 3aef98445aed..6ddcf0bc80ba 100644
--- a/src/Foundation/NSArray_1.cs
+++ b/src/Foundation/NSArray_1.cs
@@ -109,7 +109,10 @@ IEnumerator IEnumerable.GetEnumerator ()
}
#endregion
- public TKey this [nint idx] {
+ /// Gets the element at the specified index.
+ /// The zero-based index of the element to retrieve.
+ /// The element at , or if the element cannot be converted to .
+ public TKey? this [nint idx] {
get {
return GetItem ((nuint) idx);
}
diff --git a/src/Foundation/NSMutableArray_1.cs b/src/Foundation/NSMutableArray_1.cs
index 7bdf359f99a1..ee8fadecd99b 100644
--- a/src/Foundation/NSMutableArray_1.cs
+++ b/src/Foundation/NSMutableArray_1.cs
@@ -23,6 +23,7 @@
//
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
namespace Foundation {
[SupportedOSPlatform ("ios")]
@@ -193,7 +194,13 @@ public void InsertObjects (TValue [] objects, NSIndexSet atIndexes)
// Additional implementations.
- public TValue this [nuint index] {
+ /// Gets or sets the element at the specified index.
+ /// The zero-based index of the element to get or set.
+ /// The element at , or when getting if the element cannot be converted to . Setting a value is not allowed.
+ /// is .
+ /// is greater than or equal to the array's count.
+ [DisallowNull] // don't allow setting null values
+ public TValue? this [nuint index] {
get {
ValidateIndex (index);
return GetItem (index);
diff --git a/src/Foundation/NSUrlSessionConfiguration.cs b/src/Foundation/NSUrlSessionConfiguration.cs
index c0f3f17eb7f7..4978e4aa0711 100644
--- a/src/Foundation/NSUrlSessionConfiguration.cs
+++ b/src/Foundation/NSUrlSessionConfiguration.cs
@@ -70,16 +70,17 @@ public static NSUrlSessionConfiguration CreateBackgroundSessionConfiguration (st
return config;
}
+ /// Gets or sets the proxy configurations for this session.
+ /// An array of objects representing the proxy configurations.
[SupportedOSPlatform ("ios17.0")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
[SupportedOSPlatform ("tvos17.0")]
public NWProxyConfig [] ProxyConfigurations {
- get => NSArray.ArrayFromHandleFunc (_ProxyConfigurations, handle => new NWProxyConfig (handle, owns: false));
+ get => NSArray.NonNullArrayFromHandleDropNullElements (_ProxyConfigurations, handle => new NWProxyConfig (handle, owns: false));
set {
- var arr = NSArray.FromNSObjects (value);
- _ProxyConfigurations = arr.Handle;
- GC.KeepAlive (arr);
+ using var arr = NSArray.FromNSObjects (value);
+ _ProxyConfigurations = arr.GetHandle ();
}
}
diff --git a/src/Makefile.generator b/src/Makefile.generator
index 059b977890f1..752a49253e6e 100644
--- a/src/Makefile.generator
+++ b/src/Makefile.generator
@@ -4,7 +4,6 @@
# bgen.csproj.inc contains the generator_dependencies variable used to determine if the generator needs to be rebuilt or not.
$(DOTNET_BUILD_DIR)/bgen.csproj.inc: export BUILD_VERBOSITY=$(DOTNET_BUILD_VERBOSITY)
-$(DOTNET_BUILD_DIR)/bgen.csproj.inc: export DOTNET:=$(DOTNET)
$(DOTNET_BUILD_DIR)/bgen.csproj.inc: bgen/bgen.csproj.inc
$(Q) $(CP) $< $@
diff --git a/src/MediaPlayer/MPMediaQuery.cs b/src/MediaPlayer/MPMediaQuery.cs
index 6f974376d383..2737a8688ab0 100644
--- a/src/MediaPlayer/MPMediaQuery.cs
+++ b/src/MediaPlayer/MPMediaQuery.cs
@@ -9,43 +9,47 @@
// Copyright 2011-2012, 2014-2015 Xamarin, Inc
//
+#if !XAMCORE_5_0
#if !TVOS && !MONOMAC
+using System.ComponentModel;
+
#nullable enable
namespace MediaPlayer {
public partial class MPMediaQuery {
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
+ /// Returns the media item at the specified index.
+ /// The zero-based index of the item to retrieve.
+ /// The at .
+ [Obsolete ("Use the 'Items' array instead.")]
+ [EditorBrowsable (EditorBrowsableState.Never)]
public MPMediaItem GetItem (nuint index)
{
- using (var array = new NSArray (Messaging.IntPtr_objc_msgSend (Handle, Selector.GetHandle ("items"))))
- return array.GetItem (index);
+ return Items! [(int) index];
}
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
+ /// Returns the item section at the specified index.
+ /// The zero-based index of the section to retrieve.
+ /// The at .
+ [Obsolete ("Use the 'ItemSections' array instead.")]
+ [EditorBrowsable (EditorBrowsableState.Never)]
public MPMediaQuerySection GetSection (nuint index)
{
- using (var array = new NSArray (Messaging.IntPtr_objc_msgSend (Handle, Selector.GetHandle ("itemSections"))))
- return array.GetItem (index);
+ return ItemSections! [(int) index];
}
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
+ /// Returns the media item collection at the specified index.
+ /// The zero-based index of the collection to retrieve.
+ /// The at .
+ [Obsolete ("Use the 'Collections' array instead.")]
+ [EditorBrowsable (EditorBrowsableState.Never)]
public MPMediaItemCollection GetCollection (nuint index)
{
- using (var array = new NSArray (Messaging.IntPtr_objc_msgSend (Handle, Selector.GetHandle ("collections"))))
- return array.GetItem (index);
+ return Collections! [(int) index];
}
}
}
#endif // !TVOS
+#endif // !XAMCORE_5_0
diff --git a/src/Network/NWEthernetChannel.cs b/src/Network/NWEthernetChannel.cs
index c48f14253667..b99c63b04be7 100644
--- a/src/Network/NWEthernetChannel.cs
+++ b/src/Network/NWEthernetChannel.cs
@@ -113,7 +113,7 @@ public void Send (ReadOnlySpan content, ushort vlanTag, string remoteAddre
unsafe {
delegate* unmanaged trampoline = &TrampolineSendCompletion;
using var block = new BlockLiteral (trampoline, callback, typeof (NWEthernetChannel), nameof (TrampolineSendCompletion));
- var remoteAddressStr = new TransientString (remoteAddress);
+ using var remoteAddressStr = new TransientString (remoteAddress);
nw_ethernet_channel_send (GetCheckedHandle (), dispatchData.GetHandle (), vlanTag, remoteAddressStr, &block);
}
}
diff --git a/src/ObjCRuntime/Protocol.cs b/src/ObjCRuntime/Protocol.cs
index 2d711597a2e6..ca3c2bbea561 100644
--- a/src/ObjCRuntime/Protocol.cs
+++ b/src/ObjCRuntime/Protocol.cs
@@ -89,7 +89,7 @@ public static IntPtr GetHandle (string name)
internal static IntPtr objc_getProtocol (string? name)
{
- var namePtr = new TransientString (name);
+ using var namePtr = new TransientString (name);
return objc_getProtocol (namePtr);
}
diff --git a/src/PdfKit/PdfAnnotation.cs b/src/PdfKit/PdfAnnotation.cs
index 50b403680038..a8cc8843a518 100644
--- a/src/PdfKit/PdfAnnotation.cs
+++ b/src/PdfKit/PdfAnnotation.cs
@@ -76,30 +76,22 @@ public PdfAnnotationKey AnnotationType {
set { Type = value.GetConstant ()!; }
}
- /// To be added.
- /// To be added.
- /// To be added.
+ /// Gets or sets the points defining the quadrilateral bounds of the annotation.
+ /// An array of values representing the quadrilateral vertices, or .
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("maccatalyst")]
[SupportedOSPlatform ("tvos18.2")]
- public CGPoint [] QuadrilateralPoints {
+ public CGPoint []? QuadrilateralPoints {
get {
- return NSArray.ArrayFromHandleFunc (_QuadrilateralPoints, (v) => {
+ return NSArray.ArrayFromHandle (_QuadrilateralPoints, (v) => {
using (var value = new NSValue (v))
return value.CGPointValue;
});
}
set {
- if (value is null) {
- _QuadrilateralPoints = IntPtr.Zero;
- } else {
- using (var arr = new NSMutableArray ()) {
- for (int i = 0; i < value.Length; i++)
- arr.Add (NSValue.FromCGPoint (value [i]));
- _QuadrilateralPoints = arr.Handle;
- }
- }
+ using var arr = NSArray.FromNSObjects ((element) => NSValue.FromCGPoint (element), value);
+ _QuadrilateralPoints = arr.GetHandle ();
}
}
}
diff --git a/src/PdfKit/PdfKit.cs b/src/PdfKit/PdfKit.cs
index 76dcbdee7ebf..217aa5997465 100644
--- a/src/PdfKit/PdfKit.cs
+++ b/src/PdfKit/PdfKit.cs
@@ -5,52 +5,30 @@
namespace PdfKit {
partial class PdfBorder {
+ /// Gets or sets the dash pattern for the border.
+ /// An array of values defining the dash pattern, or .
public nfloat []? DashPattern {
get {
- var arr = WeakDashPattern;
- if (arr is null)
- return null;
- var rv = new nfloat [arr.Count];
- for (uint i = 0; i < rv.Length; i++)
- rv [i] = arr.GetItem (i).NFloatValue;
- return rv;
+ return WeakDashPattern?.ToArrayDropNullElements (v => v.NFloatValue);
}
set {
- if (value is null) {
- WeakDashPattern = null;
- } else {
- var arr = new NSNumber [value.Length];
- for (int i = 0; i < arr.Length; i++)
- arr [i] = new NSNumber (value [i]);
- WeakDashPattern = NSArray.FromNSObjects (arr);
- }
+ WeakDashPattern = NSArray.FromNSObjects ((v) => new NSNumber (v), value);
}
}
}
#if !IOS && !__TVOS__
- /// To be added.
- /// To be added.
+ /// Represents a PDF markup annotation such as highlight, underline, or strikethrough.
partial class PdfAnnotationMarkup {
+ /// Gets or sets the points defining the quadrilateral bounds of the markup annotation.
+ /// An array of values representing the quadrilateral vertices, or .
public CGPoint []? QuadrilateralPoints {
get {
- var arr = WeakQuadrilateralPoints;
- if (arr is null)
- return null;
- var rv = new CGPoint [arr.Count];
- for (uint i = 0; i < rv.Length; i++)
- rv [i] = arr.GetItem (i).CGPointValue;
- return rv;
+ return WeakQuadrilateralPoints?.ToArrayDropNullElements (v => v.CGPointValue);
}
set {
- if (value is null) {
- WeakQuadrilateralPoints = null;
- } else {
- var arr = new NSValue [value.Length];
- for (int i = 0; i < arr.Length; i++)
- arr [i] = NSValue.FromCGPoint (value [i]);
- WeakQuadrilateralPoints = NSArray.FromNSObjects (arr);
- }
+ using var arr = NSArray.FromNSObjects ((v) => NSValue.FromCGPoint (v), value);
+ WeakQuadrilateralPoints = arr;
}
}
}
diff --git a/src/Security/SecSharedCredential.cs b/src/Security/SecSharedCredential.cs
index b26510622551..b8d1ca262898 100644
--- a/src/Security/SecSharedCredential.cs
+++ b/src/Security/SecSharedCredential.cs
@@ -88,17 +88,33 @@ static internal class ArrayErrorActionTrampoline {
[UnmanagedCallersOnly]
internal static unsafe void Invoke (IntPtr block, IntPtr array, IntPtr err)
{
- var del = BlockLiteral.GetTarget> (block);
- if (del is not null)
- del (Runtime.GetNSObject (array), Runtime.GetNSObject (err));
+ var del = BlockLiteral.GetTarget> (block);
+ if (del is not null) {
+ var arr = NSArray.DictionaryArrayFromHandleDropNullElements (array, (dict) => new SecSharedCredentialInfo (dict));
+ del (arr, Runtime.GetNSObject (err));
+ }
}
}
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
- /// To be added.
+ /// Asynchronously requests shared web credentials from the iCloud Keychain for the specified domain and account.
+ ///
+ /// The fully qualified domain name of the website to request credentials for,
+ /// or to search all domains in the app's Associated Domains entitlement.
+ ///
+ ///
+ /// The account name to request credentials for,
+ /// or to request credentials for all accounts on the matching domain.
+ ///
+ ///
+ /// A callback invoked when the request completes, receiving an array of
+ /// with the matching credentials and an if the request failed.
+ ///
+ ///
+ ///
+ /// This method requires that the app has an Associated Domains entitlement configured
+ /// for the requested domain. The request may prompt the user for permission.
+ ///
+ ///
[SupportedOSPlatform ("ios")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
@@ -107,16 +123,8 @@ internal static unsafe void Invoke (IntPtr block, IntPtr array, IntPtr err)
[ObsoletedOSPlatform ("ios14.0", "Use 'ASAuthorizationPasswordRequest' instead.")]
[UnsupportedOSPlatform ("tvos")]
[BindingImpl (BindingImplOptions.Optimizable)]
- public static void RequestSharedWebCredential (string domainName, string account, Action handler)
+ public static void RequestSharedWebCredential (string? domainName, string? account, Action handler)
{
- Action onComplete = (NSArray a, NSError e) => {
- var creds = new SecSharedCredentialInfo [a.Count];
- int i = 0;
- foreach (var dict in NSArray.FromArrayNative (a)) {
- creds [i++] = new SecSharedCredentialInfo (dict);
- }
- handler (creds, e);
- };
// we need to create our own block literal.
using var nsDomain = (NSString?) domainName;
using var nsAccount = (NSString?) account;
diff --git a/src/VideoToolbox/VTCompressionProperties.cs b/src/VideoToolbox/VTCompressionProperties.cs
index 3997b58bf1f3..374c7b5c0e60 100644
--- a/src/VideoToolbox/VTCompressionProperties.cs
+++ b/src/VideoToolbox/VTCompressionProperties.cs
@@ -332,8 +332,8 @@ public List? DataRateLimits {
var list = new List ();
for (nuint i = 0; i < (nuint) arr.Count; i += 2) {
var rateLimit = new VTDataRateLimit (
- arr.GetItem (i).UInt32Value,
- arr.GetItem (i + 1).DoubleValue
+ arr.GetItem (i)!.UInt32Value,
+ arr.GetItem (i + 1)!.DoubleValue
);
list.Add (rateLimit);
}
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md
index 9c41970e36e6..27a4c76f89d4 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/AnalyzerReleases.Unshipped.md
@@ -23,3 +23,4 @@
| RBI0017 | Usage | Error | Field used with the wrong flag. |
| RBI0018 | Usage | Error | The export attribute must have a nonnull selector. |
| RBI0019 | Usage | Error | The selector string cannot contain any whitespace characters. |
+| RBI0042 | Usage | Error | Transient disposable type not declared with 'using'. |
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs
index 9d1c7f2f42cc..c860c5a7ce18 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.Designer.cs
@@ -1190,5 +1190,32 @@ internal static string RBI0041Title {
return ResourceManager.GetString("RBI0041Title", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup..
+ ///
+ internal static string RBI0042Description {
+ get {
+ return ResourceManager.GetString("RBI0042Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources.
+ ///
+ internal static string RBI0042MessageFormat {
+ get {
+ return ResourceManager.GetString("RBI0042MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Transient disposable type not declared with 'using'.
+ ///
+ internal static string RBI0042Title {
+ get {
+ return ResourceManager.GetString("RBI0042Title", resourceCulture);
+ }
+ }
}
}
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx
index bbcdc9608a49..8c9f22c65e52 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Resources.resx
@@ -560,4 +560,17 @@
Protocol constructor overlap:
+
+
+
+ Transient types (TransientString, TransientCFString, TransientCFObject) allocate native memory and must be disposed. Use the 'using' keyword to ensure proper cleanup.
+
+
+ Variable '{0}' of type '{1}' must be declared with the 'using' keyword to ensure proper disposal of native resources
+ {0} is the name of the variable, {1} is the type name.
+
+
+ Transient disposable type not declared with 'using'
+
+
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs
index 38241555d1d2..19845e2f408d 100644
--- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/RgenDiagnostics.cs
@@ -631,4 +631,20 @@ public static class RgenDiagnostics {
description: new LocalizableResourceString (nameof (Resources.RBI0041Description), Resources.ResourceManager,
typeof (Resources))
);
+
+ ///
+ /// Diagnostic descriptor for when a transient disposable type (TransientString, TransientCFString, TransientCFObject)
+ /// is not declared with the 'using' keyword.
+ ///
+ internal static readonly DiagnosticDescriptor RBI0042 = new (
+ "RBI0042",
+ new LocalizableResourceString (nameof (Resources.RBI0042Title), Resources.ResourceManager, typeof (Resources)),
+ new LocalizableResourceString (nameof (Resources.RBI0042MessageFormat), Resources.ResourceManager,
+ typeof (Resources)),
+ "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: new LocalizableResourceString (nameof (Resources.RBI0042Description), Resources.ResourceManager,
+ typeof (Resources))
+ );
}
diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/TransientDisposableAnalyzer.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer/TransientDisposableAnalyzer.cs
new file mode 100644
index 000000000000..9da4bedf3219
--- /dev/null
+++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/TransientDisposableAnalyzer.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using static Microsoft.Macios.Generator.RgenDiagnostics;
+
+namespace Microsoft.Macios.Bindings.Analyzer;
+
+///
+/// Analyzer to ensure that transient disposable types (TransientString, TransientCFString, TransientCFObject)
+/// are always declared with the 'using' keyword to guarantee proper disposal of native resources.
+///
+[DiagnosticAnalyzer (LanguageNames.CSharp)]
+public class TransientDisposableAnalyzer : DiagnosticAnalyzer {
+
+ static readonly ImmutableHashSet transientTypeFullNames = ImmutableHashSet.Create (
+ "ObjCRuntime.TransientString",
+ "ObjCRuntime.TransientCFString",
+ "ObjCRuntime.TransientCFObject"
+ );
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create (RBI0042);
+
+ public override void Initialize (AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
+ context.EnableConcurrentExecution ();
+ context.RegisterSyntaxNodeAction (AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
+ }
+
+ void AnalyzeNode (SyntaxNodeAnalysisContext context)
+ {
+ if (context.Node is not LocalDeclarationStatementSyntax localDeclaration)
+ return;
+
+ // If the declaration already has 'using', it's fine.
+ if (localDeclaration.UsingKeyword != default)
+ return;
+
+ foreach (var variable in localDeclaration.Declaration.Variables) {
+ var symbol = context.SemanticModel.GetDeclaredSymbol (variable);
+ if (symbol is not ILocalSymbol localSymbol)
+ continue;
+
+ var typeDisplayName = localSymbol.Type.ToDisplayString ();
+ if (!transientTypeFullNames.Contains (typeDisplayName))
+ continue;
+
+ var diagnostic = Diagnostic.Create (RBI0042, variable.GetLocation (), variable.Identifier.Text, localSymbol.Type.Name);
+ context.ReportDiagnostic (diagnostic);
+ }
+ }
+}
diff --git a/tests/Makefile b/tests/Makefile
index 3651dc51502b..a43f5bfdc685 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -149,7 +149,6 @@ $(XHARNESS_EXECUTABLE): MSBUILD_EXE_PATH=
$(XHARNESS_EXECUTABLE): $(xharness_dependencies) test.config test-system.config $(TOP)/tools/common/SdkVersions.cs
$(Q_GEN) $(DOTNET) build "/bl:$@.binlog" $(MSBUILD_VERBOSITY_QUIET) xharness/xharness.csproj
xharness/xharness.csproj.inc: export BUILD_VERBOSITY=$(DOTNET_BUILD_VERBOSITY)
-xharness/xharness.csproj.inc: export DOTNET:=$(DOTNET)
xharness/xharness.csproj.inc: export MSBUILD_EXE_PATH=
killall:
diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt
index cad51eed565b..e4dd16dd5a1b 100644
--- a/tests/cecil-tests/Documentation.KnownFailures.txt
+++ b/tests/cecil-tests/Documentation.KnownFailures.txt
@@ -20996,7 +20996,6 @@ P:Foundation.INSObjectProtocol.DebugDescription
P:Foundation.INSProgressReporting.Progress
P:Foundation.NSArchiveReplaceEventArgs.NewObject
P:Foundation.NSArchiveReplaceEventArgs.OldObject
-P:Foundation.NSArray`1.Item(System.IntPtr)
P:Foundation.NSAttributedStringDocumentAttributes.Appearance
P:Foundation.NSAttributedStringDocumentAttributes.Author
P:Foundation.NSAttributedStringDocumentAttributes.BottomMargin
@@ -21037,7 +21036,6 @@ P:Foundation.NSFileManager.TemporaryDirectory
P:Foundation.NSFileManager.UserName
P:Foundation.NSMetadataItem.UbiquitousItemDownloadingStatus
P:Foundation.NSMorphology.Unspecified
-P:Foundation.NSMutableArray`1.Item(System.UIntPtr)
P:Foundation.NSMutableData.Item(System.IntPtr)
P:Foundation.NSNetDomainEventArgs.Domain
P:Foundation.NSNetDomainEventArgs.MoreComing
@@ -21062,7 +21060,6 @@ P:Foundation.NSUbiquitousKeyValueStoreChangeEventArgs.ChangedKeys
P:Foundation.NSUbiquitousKeyValueStoreChangeEventArgs.ChangeReason
P:Foundation.NSUndoManagerCloseUndoGroupEventArgs.Discardable
P:Foundation.NSUrlAuthenticationChallenge.SenderObject
-P:Foundation.NSUrlSessionConfiguration.ProxyConfigurations
P:Foundation.NSUrlSessionConfiguration.SessionType
P:Foundation.NSUrlSessionConfiguration.StrongConnectionProxyDictionary
P:Foundation.NSUrlSessionHandler.AllowsCellularAccess
@@ -23189,8 +23186,6 @@ P:PassKit.PKVehicleConnectionSession.Delegate
P:PassKit.PKVehicleConnectionSession.WeakDelegate
P:PdfKit.IPdfViewDelegate.ParentViewController
P:PdfKit.PdfAnnotation.ActivatableTextField
-P:PdfKit.PdfAnnotationMarkup.QuadrilateralPoints
-P:PdfKit.PdfBorder.DashPattern
P:PdfKit.PdfDocument.AccessPermissions
P:PdfKit.PdfDocument.GetClassForAnnotationClass
P:PdfKit.PdfDocumentWriteOptions.AccessPermissions
diff --git a/tests/common/shared-dotnet.mk b/tests/common/shared-dotnet.mk
index 14f1e384b7d0..98236b97b959 100644
--- a/tests/common/shared-dotnet.mk
+++ b/tests/common/shared-dotnet.mk
@@ -88,9 +88,17 @@ endif
ifeq ($(RID),)
ifeq ($(PLATFORM),iOS)
+ifeq ($(shell arch),arm64)
RID=iossimulator-arm64
+else
+RID=iossimulator-x64
+endif
else ifeq ($(PLATFORM),tvOS)
+ifeq ($(shell arch),arm64)
RID=tvossimulator-arm64
+else
+RID=tvossimulator-x64
+endif
else ifeq ($(PLATFORM),MacCatalyst)
ifeq ($(CONFIG),Release)
RID=maccatalyst-x64;maccatalyst-arm64
diff --git a/tests/common/test-variations.csproj b/tests/common/test-variations.csproj
index 6826b694f495..5ab16ef80b69 100644
--- a/tests/common/test-variations.csproj
+++ b/tests/common/test-variations.csproj
@@ -18,6 +18,8 @@
+
+
@@ -111,6 +113,17 @@
<_TestVariationApplied>true
+
+ compatibility
+ <_TestVariationApplied>true
+
+
+
+ strict
+ $(DefineConstants);STATIC_NATIVE_SYMBOL_LOOKUP
+ <_TestVariationApplied>true
+
+
<_InvalidTestVariations Include="$(TestVariation.Split('|'))" Exclude="@(TestVariations)" />
diff --git a/tests/dotnet/HotReloadTestApp/AppDelegate.cs b/tests/dotnet/HotReloadTestApp/AppDelegate.cs
new file mode 100644
index 000000000000..302179724694
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/AppDelegate.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using Foundation;
+
+#nullable enable
+
+namespace HotReloadTestApp;
+
+public partial class Program {
+
+ static string Variable = "Variable has not changed";
+ static bool ContinueLooping = true;
+
+ static partial void ChangeVariable ();
+
+
+ static int Main (string [] args)
+ {
+ GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
+
+ Print (0);
+
+ for (var i = 0; i < 120 && ContinueLooping; i++) {
+ DoSomething (i + 1);
+ Thread.Sleep (TimeSpan.FromSeconds (1));
+ }
+
+ return ContinueLooping ? 1 : 0;
+ }
+
+ static void DoSomething (int i)
+ {
+ ChangeVariable ();
+ Print (i);
+ }
+
+ static string? LogPath = Environment.GetEnvironmentVariable ("HOTRELOAD_TEST_APP_LOGFILE");
+ static StreamWriter? logStream;
+ static void Print (int number)
+ {
+ var msg = $"{number} Variable={Variable}";
+ if (!string.IsNullOrEmpty (LogPath)) {
+ if (logStream is null) {
+ var fs = new FileStream (LogPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
+ logStream = new StreamWriter (fs);
+ logStream.AutoFlush = true;
+ }
+ logStream.WriteLine (msg);
+ }
+ Console.WriteLine (msg);
+ }
+}
diff --git a/tests/dotnet/HotReloadTestApp/MacCatalyst/HotReloadTestApp.csproj b/tests/dotnet/HotReloadTestApp/MacCatalyst/HotReloadTestApp.csproj
new file mode 100644
index 000000000000..6b0e2c773180
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/MacCatalyst/HotReloadTestApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst
+
+
+
diff --git a/tests/dotnet/HotReloadTestApp/MacCatalyst/Info.plist b/tests/dotnet/HotReloadTestApp/MacCatalyst/Info.plist
new file mode 100644
index 000000000000..66daae89efcc
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/MacCatalyst/Info.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ LSUIElement
+ 1
+
+
diff --git a/tests/dotnet/HotReloadTestApp/MacCatalyst/Makefile b/tests/dotnet/HotReloadTestApp/MacCatalyst/Makefile
new file mode 100644
index 000000000000..110d078f4577
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/MacCatalyst/Makefile
@@ -0,0 +1 @@
+include ../shared.mk
diff --git a/tests/dotnet/HotReloadTestApp/Makefile b/tests/dotnet/HotReloadTestApp/Makefile
new file mode 100644
index 000000000000..6affa45ff122
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/Makefile
@@ -0,0 +1,2 @@
+TOP=../../..
+include $(TOP)/tests/common/shared-dotnet-test.mk
diff --git a/tests/dotnet/HotReloadTestApp/README.md b/tests/dotnet/HotReloadTestApp/README.md
new file mode 100644
index 000000000000..ed6a66f1c7b9
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/README.md
@@ -0,0 +1 @@
+This is a test app for 'dotnet watch' (not for watchOS).
diff --git a/tests/dotnet/HotReloadTestApp/iOS/HotReloadTestApp.csproj b/tests/dotnet/HotReloadTestApp/iOS/HotReloadTestApp.csproj
new file mode 100644
index 000000000000..86d408734aa8
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/iOS/HotReloadTestApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-ios
+
+
+
diff --git a/tests/dotnet/HotReloadTestApp/iOS/Makefile b/tests/dotnet/HotReloadTestApp/iOS/Makefile
new file mode 100644
index 000000000000..110d078f4577
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/iOS/Makefile
@@ -0,0 +1 @@
+include ../shared.mk
diff --git a/tests/dotnet/HotReloadTestApp/macOS/HotReloadTestApp.csproj b/tests/dotnet/HotReloadTestApp/macOS/HotReloadTestApp.csproj
new file mode 100644
index 000000000000..a77287b9ba00
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/macOS/HotReloadTestApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-macos
+
+
+
diff --git a/tests/dotnet/HotReloadTestApp/macOS/Makefile b/tests/dotnet/HotReloadTestApp/macOS/Makefile
new file mode 100644
index 000000000000..110d078f4577
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/macOS/Makefile
@@ -0,0 +1 @@
+include ../shared.mk
diff --git a/tests/dotnet/HotReloadTestApp/shared.csproj b/tests/dotnet/HotReloadTestApp/shared.csproj
new file mode 100644
index 000000000000..2079dc97efe3
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/shared.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+
+ HotReloadTestApp
+ com.xamarin.hotreloadtestapp
+ true
+ None
+
+ true
+ true
+
+
+
+
+
+
+
+
+
diff --git a/tests/dotnet/HotReloadTestApp/shared.mk b/tests/dotnet/HotReloadTestApp/shared.mk
new file mode 100644
index 000000000000..63cdf5c07a87
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/shared.mk
@@ -0,0 +1,3 @@
+TOP=../../../..
+TESTNAME=HotReloadTestApp
+include $(TOP)/tests/common/shared-dotnet.mk
diff --git a/tests/dotnet/HotReloadTestApp/tvOS/HotReloadTestApp.csproj b/tests/dotnet/HotReloadTestApp/tvOS/HotReloadTestApp.csproj
new file mode 100644
index 000000000000..bd487ddcd88d
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/tvOS/HotReloadTestApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-tvos
+
+
+
diff --git a/tests/dotnet/HotReloadTestApp/tvOS/Makefile b/tests/dotnet/HotReloadTestApp/tvOS/Makefile
new file mode 100644
index 000000000000..110d078f4577
--- /dev/null
+++ b/tests/dotnet/HotReloadTestApp/tvOS/Makefile
@@ -0,0 +1 @@
+include ../shared.mk
diff --git a/tests/dotnet/StaticFrameworkFilterApp/AppDelegate.cs b/tests/dotnet/StaticFrameworkFilterApp/AppDelegate.cs
new file mode 100644
index 000000000000..26f5373c67a4
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/AppDelegate.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.InteropServices;
+
+using Foundation;
+
+namespace StaticFrameworkFilterApp {
+ public class Program {
+ [DllImport ("XTest.framework/XTest")]
+ static extern int theUltimateAnswer ();
+
+ static int Main (string [] args)
+ {
+ Console.WriteLine ($"Framework: {theUltimateAnswer ()}");
+
+ GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
+
+ return 0;
+ }
+ }
+}
diff --git a/tests/dotnet/StaticFrameworkFilterApp/MacCatalyst/StaticFrameworkFilterApp.csproj b/tests/dotnet/StaticFrameworkFilterApp/MacCatalyst/StaticFrameworkFilterApp.csproj
new file mode 100644
index 000000000000..6b0e2c773180
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/MacCatalyst/StaticFrameworkFilterApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst
+
+
+
diff --git a/tests/dotnet/StaticFrameworkFilterApp/iOS/StaticFrameworkFilterApp.csproj b/tests/dotnet/StaticFrameworkFilterApp/iOS/StaticFrameworkFilterApp.csproj
new file mode 100644
index 000000000000..86d408734aa8
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/iOS/StaticFrameworkFilterApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-ios
+
+
+
diff --git a/tests/dotnet/StaticFrameworkFilterApp/macOS/StaticFrameworkFilterApp.csproj b/tests/dotnet/StaticFrameworkFilterApp/macOS/StaticFrameworkFilterApp.csproj
new file mode 100644
index 000000000000..a77287b9ba00
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/macOS/StaticFrameworkFilterApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-macos
+
+
+
diff --git a/tests/dotnet/StaticFrameworkFilterApp/shared.csproj b/tests/dotnet/StaticFrameworkFilterApp/shared.csproj
new file mode 100644
index 000000000000..a1dca3b8773a
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/shared.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+
+ StaticFrameworkFilterApp
+ com.xamarin.staticframeworkfilter
+ 1.0
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/dotnet/StaticFrameworkFilterApp/tvOS/StaticFrameworkFilterApp.csproj b/tests/dotnet/StaticFrameworkFilterApp/tvOS/StaticFrameworkFilterApp.csproj
new file mode 100644
index 000000000000..bd487ddcd88d
--- /dev/null
+++ b/tests/dotnet/StaticFrameworkFilterApp/tvOS/StaticFrameworkFilterApp.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net$(BundledNETCoreAppTargetFrameworkVersion)-tvos
+
+
+
diff --git a/tests/dotnet/UnitTests/DotNetWatchTest.cs b/tests/dotnet/UnitTests/DotNetWatchTest.cs
new file mode 100644
index 000000000000..de7d789e0400
--- /dev/null
+++ b/tests/dotnet/UnitTests/DotNetWatchTest.cs
@@ -0,0 +1,219 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Xamarin.Utils;
+
+namespace Xamarin.Tests {
+ [TestFixture]
+ public class DotNetWatchTest : TestBaseClass {
+ [Test]
+ [TestCase (ApplePlatform.MacOSX)]
+ [TestCase (ApplePlatform.MacCatalyst)]
+ [TestCase (ApplePlatform.iOS)]
+ public void DotNetWatch (ApplePlatform platform)
+ {
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+
+ var projectPath = GetProjectPath ("HotReloadTestApp", platform: platform);
+ Clean (projectPath);
+
+ var projectDirectory = Path.GetDirectoryName (projectPath)!;
+
+ var tmpdir = Cache.CreateTemporaryDirectory ();
+ var additionalFile = Path.Combine (tmpdir, "AdditionalFile.cs");
+
+ var firstContent = """
+ namespace HotReloadTestApp;
+ public partial class Program {
+ static partial void ChangeVariable ()
+ {
+ Variable = "Variable will change...";
+ }
+ }
+ """;
+
+ var secondContent = """
+ namespace HotReloadTestApp;
+ public partial class Program {
+ static partial void ChangeVariable ()
+ {
+ Variable = "Variable has changed";
+ ContinueLooping = false;
+ }
+ }
+ """;
+
+ File.WriteAllText (additionalFile, firstContent);
+
+ // Debug logging is annoying here, because the test runner captures stdout/stderr, so it won't be visible until the test fails,
+ // which can take a while because when things go wrong here it will most likely result in timeouts.
+ // So instead we log to a separate file, which can be viewed as the test is running.
+ var debugLogPath = Path.Combine (tmpdir, "debug.log");
+ using var debugLog = new StreamWriter (File.OpenWrite (debugLogPath)) {
+ AutoFlush = true,
+ };
+
+ var output = new List ();
+ var appStarted = new TaskCompletionSource ();
+ var waitingForChanges = new TaskCompletionSource ();
+ var variableChanged = new TaskCompletionSource ();
+ var cts = new CancellationTokenSource ();
+ var appOutput = new List ();
+
+ var outputProcessor = new Action (line => {
+ if (line.Contains ("Variable has not changed")) {
+ if (appStarted.TrySetResult (true))
+ debugLog.WriteLine ("Got 'Variable has not changed'");
+ }
+ if (line.Contains ("Variable has changed")) {
+ if (variableChanged.TrySetResult (true))
+ debugLog.WriteLine ("Got 'Variable has changed'");
+ }
+ if (line.Contains ("Waiting for changes")) {
+ waitingForChanges.TrySetResult (true);
+ debugLog.WriteLine ("Got 'Waiting for changes'");
+ }
+ });
+
+ // I'm not sure what 'dotnet watch' does with the terminal, but Console.WriteLine from the test app doesn't seem to
+ // reliably be captured here, so instead we have the test app write its output to a file, and we poll that file and
+ // process new lines as they are written.
+ // However, for mobile platforms, test app stdout is captured correctly, so we process both the output from the file
+ // and stdout we capture from 'dotnet watch' the same way, to make sure we don't miss any output.
+ var logPath = Path.Combine (tmpdir, "output.log");
+ var pollThread = new Thread ((v) => {
+ for (var i = 0; i < 120; i++) {
+ if (File.Exists (logPath)) {
+ var lines = File.ReadAllLines (logPath);
+ Array.ForEach (lines, outputProcessor);
+ lock (appOutput) {
+ appOutput.Clear ();
+ appOutput.AddRange (lines);
+ }
+ }
+ Thread.Sleep (TimeSpan.FromSeconds (1));
+ }
+ }) {
+ IsBackground = true,
+ };
+ pollThread.Start ();
+
+ Action outputCallback = (line) => {
+ debugLog.WriteLine ($"[dotnet watch] {line}");
+ lock (output) {
+ output.Add (line);
+ outputProcessor (line);
+ }
+ };
+
+ var args = new List {
+ "watch",
+ "--non-interactive",
+ };
+
+ if (platform == ApplePlatform.iOS || platform == ApplePlatform.TVOS) {
+ var runtimeIdentifier = GetDefaultRuntimeIdentifier (platform);
+ var device = GetDeviceAsync (projectDirectory, runtimeIdentifier).GetAwaiter ().GetResult ();
+ debugLog.WriteLine ($"Using device: {device}");
+ args.Add ($"--device={device}");
+ }
+
+ var env = new Dictionary {
+ { "HOTRELOAD_TEST_APP_LOGFILE", logPath },
+ { "AdditionalFile", additionalFile },
+ };
+
+ var watchTask = Execution.RunWithCallbacksAsync (
+ DotNet.Executable,
+ args,
+ environment: env,
+ standardOutput: outputCallback,
+ standardError: outputCallback,
+ workingDirectory: projectDirectory,
+ timeout: TimeSpan.FromMinutes (10),
+ cancellationToken: cts.Token,
+ log: debugLog
+ );
+
+ // Wait for the app to start and show initial output
+ debugLog.WriteLine ("Waiting for app start...");
+ if (!appStarted.Task.Wait (TimeSpan.FromMinutes (1)))
+ Assert.Fail ($"Timed out waiting for the app to start. Output:\n{string.Join ("\n", output)}\nDebug output:\n{string.Join ("\n", File.ReadAllLines (debugLogPath))}");
+ debugLog.WriteLine ("App started!");
+
+ debugLog.WriteLine ("Waiting for 'dotnet watch' to be waiting for changes...");
+ if (!waitingForChanges.Task.Wait (TimeSpan.FromMinutes (1)))
+ Assert.Fail ($"Timed out waiting for the 'dotnet watch' to be waiting for changes. Output:\n{string.Join ("\n", output)}\nDebug output:\n{string.Join ("\n", File.ReadAllLines (debugLogPath))}");
+ debugLog.WriteLine ("Waiting for changes!");
+
+ // Write AdditionalFile.cs to trigger a rebuild via dotnet watch
+ File.WriteAllText (additionalFile, secondContent);
+
+ // Wait for dotnet watch to pick up the change and the app to show the updated output
+ debugLog.WriteLine ("Waiting for app restart...");
+ if (!variableChanged.Task.Wait (TimeSpan.FromMinutes (1)))
+ Assert.Fail ($"Timed out waiting for the variable to change. Output:\n{string.Join ("\n", output)}\nDebug output:\n{string.Join ("\n", File.ReadAllLines (debugLogPath))}");
+ debugLog.WriteLine ("App restarted!");
+
+ // Cancel the watch process
+ debugLog.WriteLine ("Terminating the watch process...");
+ cts.Cancel ();
+
+ try {
+ debugLog.WriteLine ("Waiting for exit...");
+ watchTask.Wait (TimeSpan.FromSeconds (30));
+ debugLog.WriteLine ("Waited for exit");
+ } catch {
+ // Expected - the process was cancelled
+ }
+ }
+
+ // Pick any device for the specified project, and compatible with the specified runtime identifier (if provided).
+ // We just need any device to test that dotnet watch can detect it and deploy to it.
+ static async Task GetDeviceAsync (string projectDirectory, string? runtimeIdentifier = null)
+ {
+ var tmpdir = Cache.CreateTemporaryDirectory ();
+ var outputFile = Path.Combine (tmpdir, "AvailableDevices.json");
+ var args = new List {
+ "build",
+ "-t:ComputeAvailableDevices",
+ "-getItem:Devices",
+ $"-getResultOutputFile:{outputFile}",
+ };
+
+ if (!string.IsNullOrEmpty (runtimeIdentifier))
+ args.Add ($"-p:RuntimeIdentifier={runtimeIdentifier}");
+
+ var rv = await Execution.RunWithCallbacksAsync (
+ DotNet.Executable,
+ args,
+ workingDirectory: projectDirectory,
+ timeout: TimeSpan.FromMinutes (1),
+ log: Console.Out
+ );
+ Assert.That (rv.ExitCode, Is.EqualTo (0), "Failed to compute available devices");
+
+ var output = File.ReadAllText (outputFile);
+ var doc = JsonDocument.Parse (output);
+ // The devices are ordered, so that:
+ // * We get the same device each time, to make tests more reliable.
+ // * We get the most recent OS version available, to make sure we're testing on a recent OS version.
+ // * We get iPhones before iPads (by sorting by device type identifier), just because they take up less of the screen during a test run.
+ var devices = doc.RootElement.GetProperty ("Items").GetProperty ("Devices").EnumerateArray ().Select (e => {
+ var identity = e.GetProperty ("Identity").GetString ()!;
+ var osVersion = Version.Parse (e.GetProperty ("OSVersion").GetString ()!);
+ var deviceTypeIdentifier = e.GetProperty ("DeviceTypeIdentifier").GetString ()!;
+ return (Identity: identity, OsVersion: osVersion, DeviceTypeIdentifier: deviceTypeIdentifier);
+ }).OrderBy (d => d.OsVersion).ThenByDescending (d => d.DeviceTypeIdentifier).ThenBy (d => d.Identity).ToList ();
+ if (!devices.Any ())
+ Assert.Inconclusive ("No devices found. Output:\n" + output);
+ return devices.First ().Identity;
+ }
+ }
+}
diff --git a/tests/dotnet/UnitTests/PostBuildTest.cs b/tests/dotnet/UnitTests/PostBuildTest.cs
index df62e5a99074..c03a488b39c0 100644
--- a/tests/dotnet/UnitTests/PostBuildTest.cs
+++ b/tests/dotnet/UnitTests/PostBuildTest.cs
@@ -354,6 +354,43 @@ public void BundleStructureDSyms (ApplePlatform platform, string runtimeIdentifi
AssertExpectedDSyms (platform, appPath);
}
+ [Test]
+ [TestCase (ApplePlatform.iOS, "iossimulator-arm64")]
+ [TestCase (ApplePlatform.TVOS, "tvossimulator-arm64")]
+ [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64")]
+ [TestCase (ApplePlatform.MacOSX, "osx-arm64")]
+ public void StaticFrameworksNotInPostProcessing (ApplePlatform platform, string runtimeIdentifiers)
+ {
+ // https://github.com/dotnet/macios/issues/24840
+ // This test does a Release build, which enables dsymutil/strip post-processing.
+ // Without the fix, the build would fail because dsymutil would try to process a
+ // static framework that is not present in the app bundle.
+ var project = "StaticFrameworkFilterApp";
+ var configuration = "Release";
+ Configuration.IgnoreIfIgnoredPlatform (platform);
+ Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers);
+
+ var project_path = GetProjectPath (project, runtimeIdentifiers, platform, out var appPath, configuration: configuration);
+ Clean (project_path);
+ var properties = GetDefaultProperties (runtimeIdentifiers);
+ properties ["Configuration"] = configuration;
+ // macOS and Mac Catalyst default to NoDSymUtil=true (dSYMs only generated when archiving),
+ // so explicitly disable it to test dSYM generation.
+ properties ["NoDSymUtil"] = "false";
+
+ var result = DotNet.AssertBuild (project_path, properties);
+ var postProcessingItems = GetPostProcessingItems (result.BinLogPath);
+
+ // The dynamic framework (XTest) should be in the post-processing items
+ var dynamicFrameworkItems = postProcessingItems.Where (i => i.ItemSpec.Contains ("XTest.framework/XTest")).ToList ();
+ Assert.That (dynamicFrameworkItems.Count, Is.EqualTo (1), $"Expected 1 XTest framework post-processing item, got {dynamicFrameworkItems.Count}. All items:\n\t{string.Join ("\n\t", postProcessingItems.Select (i => i.ItemSpec))}");
+
+ // The static framework (XStaticArTest) should NOT be in the post-processing items,
+ // because it's a static library and won't be in the app bundle.
+ var staticFrameworkItems = postProcessingItems.Where (i => i.ItemSpec.Contains ("XStaticArTest")).ToList ();
+ Assert.That (staticFrameworkItems, Is.Empty, $"Static framework XStaticArTest should not be in post-processing items. All items:\n\t{string.Join ("\n\t", postProcessingItems.Select (i => i.ItemSpec))}");
+ }
+
static List GetPostProcessingItems (string binLogPath)
{
var items = new Dictionary ();
diff --git a/tests/monotouch-test/CoreFoundation/ProxyTest.cs b/tests/monotouch-test/CoreFoundation/ProxyTest.cs
index 99c7c214d68b..3dc46ece6d40 100644
--- a/tests/monotouch-test/CoreFoundation/ProxyTest.cs
+++ b/tests/monotouch-test/CoreFoundation/ProxyTest.cs
@@ -18,23 +18,6 @@ namespace MonoTouchFixtures.CoreFoundation {
[TestFixture]
[Preserve (AllMembers = true)]
public class ProxyTest {
-
- [Test]
- public void Fields ()
- {
- // documented but symbols are missing
- // this test will fail if Apple decide to include them in the future
- IntPtr lib = Dlfcn.dlopen (Constants.CoreFoundationLibrary, 0);
- try {
- // http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFProxySupport/Reference/reference.html#//apple_ref/doc/c_ref/kCFProxyAutoConfigurationHTTPResponseKey
- Assert.That (Dlfcn.dlsym (lib, "kCFProxyAutoConfigurationHTTPResponseKey"), Is.EqualTo (IntPtr.Zero), "kCFProxyAutoConfigurationHTTPResponseKey");
- // http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFProxySupport/Reference/reference.html#//apple_ref/doc/c_ref/kCFNetworkProxiesProxyAutoConfigJavaScript
- Assert.That (Dlfcn.dlsym (lib, "kCFNetworkProxiesProxyAutoConfigJavaScript"), Is.EqualTo (IntPtr.Zero), "kCFNetworkProxiesProxyAutoConfigJavaScript");
- } finally {
- Dlfcn.dlclose (lib);
- }
- }
-
#if !MONOMAC
HttpListener listener;
int port;
diff --git a/tests/monotouch-test/Foundation/AttributedStringTest.cs b/tests/monotouch-test/Foundation/AttributedStringTest.cs
index 62ffbdbdd702..4844f9885cbb 100644
--- a/tests/monotouch-test/Foundation/AttributedStringTest.cs
+++ b/tests/monotouch-test/Foundation/AttributedStringTest.cs
@@ -52,28 +52,6 @@ void cb (NSDictionary attrs, NSRange range, ref bool stop)
failEnum = true;
}
- [Test]
- public void Fields ()
- {
- // fields are not available in iOS (at least up to 5.1.1)
- // this test will fail if this ever change in the future
- IntPtr lib = Dlfcn.dlopen (Constants.FoundationLibrary, 0);
- try {
- Assert.That (Dlfcn.dlsym (lib, "NSFontAttributeName"), Is.EqualTo (IntPtr.Zero), "NSFontAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSLinkAttributeName"), Is.EqualTo (IntPtr.Zero), "NSLinkAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSUnderlineStyleAttributeName"), Is.EqualTo (IntPtr.Zero), "NSUnderlineStyleAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSStrikethroughStyleAttributeName"), Is.EqualTo (IntPtr.Zero), "NSStrikethroughStyleAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSStrokeWidthAttributeName"), Is.EqualTo (IntPtr.Zero), "NSStrokeWidthAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSParagraphStyleAttributeName"), Is.EqualTo (IntPtr.Zero), "NSParagraphStyleAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSForegroundColorAttributeName"), Is.EqualTo (IntPtr.Zero), "NSForegroundColorAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSBackgroundColorAttributeName"), Is.EqualTo (IntPtr.Zero), "NSBackgroundColorAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSLigatureAttributeName"), Is.EqualTo (IntPtr.Zero), "NSLigatureAttributeName");
- Assert.That (Dlfcn.dlsym (lib, "NSObliquenessAttributeName"), Is.EqualTo (IntPtr.Zero), "NSObliquenessAttributeName");
- } finally {
- Dlfcn.dlclose (lib);
- }
- }
-
[Test]
public void UIKitAttachmentConveniences_New ()
{
diff --git a/tests/monotouch-test/ObjCRuntime/DlfcnTest.cs b/tests/monotouch-test/ObjCRuntime/DlfcnTest.cs
index aeda262d8b76..8fa51299cbbc 100644
--- a/tests/monotouch-test/ObjCRuntime/DlfcnTest.cs
+++ b/tests/monotouch-test/ObjCRuntime/DlfcnTest.cs
@@ -13,6 +13,53 @@ namespace MonoTouchFixtures.ObjCRuntime {
[Preserve (AllMembers = true)]
public class DlfcnTest {
+ // These tests exercise [Field]-backed properties from Apple frameworks.
+ // The generated binding code calls Dlfcn.GetStringConstant / GetIntPtr / etc.
+ // under the hood, which is what InlineDlfcnMethodsStep transforms.
+
+ [Test]
+ public void StringConstant_NSLocaleNotification ()
+ {
+ var value = NSLocale.CurrentLocaleDidChangeNotification;
+ Assert.IsNotNull (value, "CurrentLocaleDidChangeNotification");
+ Assert.AreEqual ("kCFLocaleCurrentLocaleDidChangeNotification", (string) value, "value");
+ }
+
+ [Test]
+ public void StringConstant_NSBundleNotification ()
+ {
+ var value = NSBundle.BundleDidLoadNotification;
+ Assert.IsNotNull (value, "BundleDidLoadNotification");
+ Assert.AreEqual ("NSBundleDidLoadNotification", (string) value, "value");
+ }
+
+ [Test]
+ public void StringConstant_NSUserDefaultsNotification ()
+ {
+ var value = NSUserDefaults.DidChangeNotification;
+ Assert.IsNotNull (value, "DidChangeNotification");
+ Assert.AreEqual ("NSUserDefaultsDidChangeNotification", (string) value, "value");
+ }
+
+ [Test]
+ public void StringConstant_NSUndoManagerNotification ()
+ {
+ var value = NSUndoManager.CheckpointNotification;
+ Assert.IsNotNull (value, "CheckpointNotification");
+ Assert.AreEqual ("NSUndoManagerCheckpointNotification", (string) value, "value");
+ }
+
+ [Test]
+ public void StringConstant_CachePointer ()
+ {
+ // Access several string constants multiple times to test caching behavior.
+ // The binding code uses Dlfcn.CachePointer for repeated accesses.
+ for (int i = 0; i < 3; i++) {
+ var value = NSLocale.CurrentLocaleDidChangeNotification;
+ Assert.IsNotNull (value, $"iteration {i}");
+ }
+ }
+
[Test]
public void OpenClose_libSystem ()
{
@@ -32,7 +79,7 @@ public void OpenClose_libSystem ()
[Test]
public void GetVariables ()
{
- var symbol = "x_native_field";
+ const string symbol = "x_native_field";
var handle = (IntPtr) Dlfcn.RTLD.Default;
Assert.AreNotEqual (IntPtr.Zero, Dlfcn.dlsym (handle, symbol), "Symbol");
@@ -63,8 +110,10 @@ public void GetVariables ()
Assert.AreEqual (-3.9541907E+28f, Dlfcn.GetStruct (handle, symbol), "GetStruct");
Assert.AreEqual (-7.7576533930025207E-103d, Dlfcn.GetStruct (handle, symbol), "GetStruct");
+#if !STATIC_NATIVE_SYMBOL_LOOKUP
Assert.AreEqual ((ulong) 0, Dlfcn.GetStruct (handle, "inexistent_symbol"), "GetStruct inexistent");
Assert.AreEqual ((ulong) 0, Dlfcn.GetStruct (handle, "inexistent_symbol").Value, "GetStruct inexistent");
+#endif
Dlfcn.SetInt16 (handle, symbol, 0x77);
Assert.AreEqual ((short) 0x77, Dlfcn.GetInt16 (handle, symbol), "SetInt16");
@@ -122,5 +171,24 @@ struct SomeValue {
public ulong Value;
}
#pragma warning restore CS0649
+
+ [Test]
+ public void FieldProperty_CGRect ()
+ {
+ Assert.Multiple (() => {
+ // CGRect.Null is backed by [Field("CGRectNull")] which calls Dlfcn.GetCGRect.
+ var value = global::CoreGraphics.CGRect.Null;
+ Assert.That (value.X, Is.EqualTo (nfloat.PositiveInfinity), "CGRectNull.X");
+ Assert.That (value.Y, Is.EqualTo (nfloat.PositiveInfinity), "CGRectNull.Y");
+ Assert.That (value.Width, Is.EqualTo ((nfloat) 0), "CGRectNull.Width");
+ Assert.That (value.Height, Is.EqualTo ((nfloat) 0), "CGRectNull.Height");
+
+ var infinite = global::CoreGraphics.CGRect.Infinite;
+ Assert.That (infinite.X, Is.EqualTo (nfloat.MinValue / 2), "CGRectInfinite.X");
+ Assert.That (infinite.Y, Is.EqualTo (nfloat.MinValue / 2), "CGRectInfinite.Y");
+ Assert.That (infinite.Width, Is.EqualTo (nfloat.MaxValue), "CGRectInfinite.Width");
+ Assert.That (infinite.Height, Is.EqualTo (nfloat.MaxValue), "CGRectInfinite.Height");
+ });
+ }
}
}
diff --git a/tests/monotouch-test/Security/RecordTest.cs b/tests/monotouch-test/Security/RecordTest.cs
index 79e6aa29f7ee..af4a398bb6d4 100644
--- a/tests/monotouch-test/Security/RecordTest.cs
+++ b/tests/monotouch-test/Security/RecordTest.cs
@@ -310,29 +310,38 @@ public void DeskCase_83099_InmutableDictionary ()
{
var testUsername = "testusername";
- //TEST 1: Save a keychain value
- var test1 = SaveUserPassword (testUsername, "testValue1", out var queryCode, out var addCode, out var updateCode);
- Assert.IsTrue (test1, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}");
+ // Clean up any stale keychain entries from previous test runs to avoid
+ // the keychain returning ItemNotFound on query but DuplicateItem on add.
+ ForceRemoveUserPassword (testUsername);
- //TEST 2: Get the saved keychain value
- var test2 = GetUserPassword (testUsername);
- Assert.IsTrue (StringUtil.StringsEqual (test2, "testValue1", false));
+ try {
+ //TEST 1: Save a keychain value
+ var test1 = SaveUserPassword (testUsername, "testValue1", out var queryCode, out var addCode, out var updateCode);
+ Assert.IsTrue (test1, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}");
+
+ //TEST 2: Get the saved keychain value
+ var test2 = GetUserPassword (testUsername);
+ Assert.IsTrue (StringUtil.StringsEqual (test2, "testValue1", false));
- //TEST 3: Update the keychain value
- var test3 = SaveUserPassword (testUsername, "testValue2", out queryCode, out addCode, out updateCode);
- Assert.IsTrue (test3, "Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}");
+ //TEST 3: Update the keychain value
+ var test3 = SaveUserPassword (testUsername, "testValue2", out queryCode, out addCode, out updateCode);
+ Assert.IsTrue (test3, $"Password could not be saved to keychain. queryCode: {queryCode} addCode: {addCode} updateCode: {updateCode}");
- //TEST 4: Get the updated keychain value
- var test4 = GetUserPassword (testUsername);
- Assert.IsTrue (StringUtil.StringsEqual (test4, "testValue2", false));
+ //TEST 4: Get the updated keychain value
+ var test4 = GetUserPassword (testUsername);
+ Assert.IsTrue (StringUtil.StringsEqual (test4, "testValue2", false));
- //TEST 5: Clear the keychain values
- var test5 = ClearUserPassword (testUsername);
- Assert.IsTrue (test5, "Password could not be cleared from keychain");
+ //TEST 5: Clear the keychain values
+ var test5 = ClearUserPassword (testUsername);
+ Assert.IsTrue (test5, "Password could not be cleared from keychain");
- //TEST 6: Verify no keychain value
- var test6 = GetUserPassword (testUsername);
- Assert.IsNull (test6, "No password should exist here");
+ //TEST 6: Verify no keychain value
+ var test6 = GetUserPassword (testUsername);
+ Assert.IsNull (test6, "No password should exist here");
+ } finally {
+ // Always clean up to avoid leaving stale entries for subsequent runs
+ ForceRemoveUserPassword (testUsername);
+ }
}
public static string GetUserPassword (string username)
@@ -367,6 +376,13 @@ record = CreateSecRecord (SecKind.InternetPassword,
);
addCode = SecKeyChain.Add (record);
success = (addCode == SecStatusCode.Success);
+ // Handle inconsistent keychain state: query returned ItemNotFound
+ // but add returned DuplicateItem. Force-remove and retry.
+ if (addCode == SecStatusCode.DuplicateItem) {
+ SecKeyChain.Remove (searchRecord);
+ addCode = SecKeyChain.Add (record);
+ success = (addCode == SecStatusCode.Success);
+ }
}
if (queryCode == SecStatusCode.Success && record is not null) {
record.ValueData = NSData.FromString (password);
@@ -376,6 +392,15 @@ record = CreateSecRecord (SecKind.InternetPassword,
return success;
}
+ public static void ForceRemoveUserPassword (string username)
+ {
+ var searchRecord = CreateSecRecord (SecKind.InternetPassword,
+ server: "Test1",
+ account: username.ToLower ()
+ );
+ SecKeyChain.Remove (searchRecord);
+ }
+
public static bool ClearUserPassword (string username)
{
var success = false;
diff --git a/tests/monotouch-test/Security/SecSharedCredentialTest.cs b/tests/monotouch-test/Security/SecSharedCredentialTest.cs
index 7a3e4afd8d24..f1bf50ba6905 100644
--- a/tests/monotouch-test/Security/SecSharedCredentialTest.cs
+++ b/tests/monotouch-test/Security/SecSharedCredentialTest.cs
@@ -19,9 +19,6 @@ public class SecSharedCredentialTest {
[SetUp]
public void SetUp ()
{
- // The API here was introduced to Mac Catalyst later than for the other frameworks, so we have this additional check
- TestRuntime.AssertSystemVersion (ApplePlatform.MacCatalyst, 14, 0, throwIfOtherPlatform: false);
-
domainName = "com.xamarin.monotouch-test";
account = "twitter";
password = "12345678";
@@ -52,8 +49,6 @@ public void AddSharedWebCredentialNullAccount ()
[Timeout (5000)]
public void AddSharedWebCredentialNotNullPassword ()
{
- TestRuntime.AssertSystemVersion (ApplePlatform.iOS, 8, 0, throwIfOtherPlatform: false);
-
Action handler = (NSError e) => {
// we do nothing, if we did block the test should be interactive because a dialog is shown.
};
@@ -66,8 +61,6 @@ public void AddSharedWebCredentialNotNullPassword ()
[Timeout (5000)]
public void AddSharedWebCredentialNullPassword ()
{
- TestRuntime.AssertSystemVersion (ApplePlatform.iOS, 8, 0, throwIfOtherPlatform: false);
-
password = null;
Action handler = (NSError e) => {
// we do nothing, if we did block the test should be interactive because a dialog is shown.
@@ -76,10 +69,32 @@ public void AddSharedWebCredentialNullPassword ()
}
[Test]
- public void CreateSharedWebCredentialPassword ()
+ // We do not want to block for a long period of time if the event is not set.
+ // We are testing the fact that the trampoline works.
+ [Timeout (5000)]
+ public void RequestSharedWebCredentialTest ()
+ {
+ Action handler = (SecSharedCredentialInfo [] creds, NSError e) => {
+ // we do nothing, if we did block the test should be interactive because a dialog is shown.
+ };
+ SecSharedCredential.RequestSharedWebCredential (domainName, account, handler);
+ }
+
+ [Test]
+ // We do not want to block for a long period of time if the event is not set.
+ // We are testing the fact that the trampoline works.
+ [Timeout (5000)]
+ public void RequestSharedWebCredentialNullDomainAndAccountTest ()
{
- TestRuntime.AssertSystemVersion (ApplePlatform.iOS, 8, 0, throwIfOtherPlatform: false);
+ Action handler = (SecSharedCredentialInfo [] creds, NSError e) => {
+ // we do nothing, if we did block the test should be interactive because a dialog is shown.
+ };
+ SecSharedCredential.RequestSharedWebCredential (null, null, handler);
+ }
+ [Test]
+ public void CreateSharedWebCredentialPassword ()
+ {
var pwd = SecSharedCredential.CreateSharedWebCredentialPassword ();
Assert.IsNotNull (pwd);
}
diff --git a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs
index 628c45094e57..34e018063694 100644
--- a/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs
+++ b/tests/monotouch-test/System.Net.Http/NSUrlSessionHandlerTest.cs
@@ -59,6 +59,24 @@ public void DisposeAndRecreateBackgroundSessionHandler ()
IgnoreIfExceptionDueToBackgroundServiceInUseByAnotherProcess (ex);
TestRuntime.IgnoreInCIIfBadNetwork (ex);
+ if (ex is ObjCException && ex.ToString ().Contains ("Task created in a session that has been invalidated")) {
+ // When disposing an NSUrlSessionHandler backed by a background NSUrlSession
+ // and immediately creating a new handler with the same background session
+ // identifier, the new session can fail with 'Task created in a session
+ // that has been invalidated'.
+ //
+ // This happens because InvalidateAndCancel() is asynchronous - it marks
+ // the session for invalidation but doesn't wait for it to complete. Apple
+ // reuses the same native session object for background sessions with the
+ // same identifier, so creating a new session before invalidation completes
+ // returns the already-invalidated session.
+ //
+ // There are a couple of fixes:
+ // * Add a Thread.Sleep before creating the second NSUrlSessionHandler - but this will slow down every test run,
+ // * Wait for the session to become invalid in NSUrlSessionHandler (add a 'DidBecomeInvalid' implementation, and wait for that in Dispose) - which may unnecessarily slow down working code.
+ // * Detect this scenario here, and just mark the test as inconclusive. The test does something somewhat unusual (create two background sessions with the same identifier in quick succession), so this seems like the best approach for now.
+ Assert.Inconclusive ("The previous background session wasn't fully invalidated before we tried to create a new background session (with the same identifier)");
+ }
Assert.IsNull (ex, "Second request exception");
}
diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/TransientDisposableAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/TransientDisposableAnalyzerTests.cs
new file mode 100644
index 000000000000..81bd6480bdb4
--- /dev/null
+++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/TransientDisposableAnalyzerTests.cs
@@ -0,0 +1,204 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Xamarin.Tests;
+using Xamarin.Utils;
+using Xunit;
+
+namespace Microsoft.Macios.Bindings.Analyzer.Tests;
+
+public class TransientDisposableAnalyzerTests : BaseGeneratorWithAnalyzerTestClass {
+ class ErrorTestCases : IEnumerable