diff --git a/.github/workflows/autoformat-v2.yml b/.github/workflows/autoformat-v2.yml index b7ead504ef9b..d906a323359b 100644 --- a/.github/workflows/autoformat-v2.yml +++ b/.github/workflows/autoformat-v2.yml @@ -21,7 +21,7 @@ jobs: persist-credentials: false - name: 'Install .NET' - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: ./global.json @@ -67,7 +67,7 @@ jobs: fetch-depth: 1 - name: 'Install .NET' - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: global-json-file: ./global.json diff --git a/.github/workflows/maestro-changelog.yml b/.github/workflows/maestro-changelog.yml index ab0ebf924389..c976b9e161fb 100644 --- a/.github/workflows/maestro-changelog.yml +++ b/.github/workflows/maestro-changelog.yml @@ -17,7 +17,7 @@ jobs: if: github.event.pull_request.user.login == 'dotnet-maestro[bot]' steps: - - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 + - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 with: dotnet-version: '9' diff --git a/docs/api/Foundation/ModelAttribute.xml b/docs/api/Foundation/ModelAttribute.xml deleted file mode 100644 index 661a5b458cf8..000000000000 --- a/docs/api/Foundation/ModelAttribute.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Flag a class as a model. - - Objective-C protocols are like interfaces, but they support optional methods, that is, not all of the methods need to be implemented for the protocol to work. - -There are two ways of implementing a model, you can either implement it manually or use the existing strongly typed definitions. - - -MonoTouch provides already strongly typed declarations ready to use that do not require manual binding. To support this programming model, the MonoTouch runtime supports the [Model] attribute on a class declaration. This informs the runtime that it should not wire up all the methods in the class, unless the method is explicitly implemented. - - -The Model attribute is applied to a class that can have optional methods, and it is typically used for declaring Objective-C delegates or data models that have a number of optional methods. The MonoTouch runtime treats classes with the Model attribute applied specially: only when a user overrides methods in a class, is the actual override exposed to the Objective-C world as existing. - - -This attribute is used in all of the models and delegate classes to allow the user to only implement the methods that he is interested in. - - - - \ No newline at end of file diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 78a10c4c2d9d..97525d1af61e 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -466,6 +466,23 @@ The full path to the `ditto` executable. The default behavior is to use `/usr/bin/ditto`. +## DynamicRegistrationSupported + +Controls whether the dynamic registrar is available at runtime (as reported by +`ObjCRuntime.Runtime.DynamicRegistrationSupported`). + +If this value is not specified, the build will compute a default value based +on whether the app needs the dynamic registrar, and enables the +`ObjCRuntime.Runtime.DynamicRegistrationSupported` trimmer feature switch +accordingly (so the trimmer can remove the dynamic registrar when it's not +needed, making the app smaller). + +Set this property to `true` or `false` to override the computed default. + +Removing the dynamic registrar requires a static registrar (`Registrar=static` or +`Registrar=managed-static`) and trimming, so setting this property has no effect (and the +build warns) when those conditions aren't met. + ## EmbedOnDemandResources If on-demand resources should be embedded in the app bundle. diff --git a/dotnet/Workloads/SignList.xml b/dotnet/Workloads/SignList.xml index 8c695597ea18..e674d1aa5c15 100644 --- a/dotnet/Workloads/SignList.xml +++ b/dotnet/Workloads/SignList.xml @@ -37,18 +37,15 @@ - - - @@ -58,6 +55,9 @@ + + + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d0e5e10e5d2f..4c11d8f1ee04 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -272,6 +272,7 @@ _ExtendAppExtensionReferences; _ComputeLinkerArguments; _PrepareAssemblies; + _SetDynamicRegistrationSupportedFeature; _ComputeFrameworkFilesToPublish; _ComputeDynamicLibrariesToPublish; ComputeFilesToPublish; @@ -427,6 +428,8 @@ Targets="_BuildRidSpecificAppBundle" Properties=" AppendRuntimeIdentifierToOutputPath=true; + BuildSessionId=$(BuildSessionId); + BuildAppName=$(BuildAppName); RuntimeIdentifier=%(_RuntimeIdentifiersAsItems.Identity); _CodesignItemsPath=%(_RuntimeIdentifiersAsItems.RidSpecificCodesignItemsPath); _MtouchSymbolsList=%(_RuntimeIdentifiersAsItems.RidSpecificSymbolsListPath); @@ -560,6 +563,37 @@ + + + + + + + <_DynamicRegistrationSupportedLine Include="@(_AssemblyPreparerMSBuildOutputProperty)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('DynamicRegistrationSupported='))" /> + + + + <_DynamicRegistrationSupportedLineText>@(_DynamicRegistrationSupportedLine) + <_DynamicRegistrationSupported>$(DynamicRegistrationSupported) + <_DynamicRegistrationSupported Condition="'$(_DynamicRegistrationSupported)' == '' And '$(_DynamicRegistrationSupportedLineText)' != ''">$(_DynamicRegistrationSupportedLineText.Replace('DynamicRegistrationSupported=', '')) + + + + + + @@ -607,6 +641,10 @@ <_CustomLinkerOptionsFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)custom-linker-options.txt')) <_CustomLinkerOptionsFile Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)custom-linker-options.txt + + <_AssemblyPreparerMSBuildOutputFile Condition="'$(PrepareAssemblies)' == 'true'">$(DeviceSpecificIntermediateOutputPath)assembly-preparer-msbuild-output.txt + <_AssemblyPostProcessorMSBuildOutputFile Condition="'$(PrepareAssemblies)' == 'true'">$(DeviceSpecificIntermediateOutputPath)assembly-postprocessor-msbuild-output.txt + <_LinkerItemsDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-items')) <_LinkerItemsDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-items @@ -649,6 +687,7 @@ @(_BundlerDlsym -> 'Dlsym=%(Identity)') Debug=$(_BundlerDebug) DeploymentTarget=$(_MinimumOSVersion) + DynamicRegistrationSupported=$(DynamicRegistrationSupported) @(_CustomLinkFlags -> 'CustomLinkFlags=%(Identity)') EnableSGenConc=$(EnableSGenConc) @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)') @@ -668,6 +707,8 @@ MarshalManagedExceptionMode=$(MarshalManagedExceptionMode) MarshalObjectiveCExceptionMode=$(MarshalObjectiveCExceptionMode) @(_MonoLibrary -> 'MonoLibrary=%(Identity)') + MSBuildOutputFile=$(_AssemblyPreparerMSBuildOutputFile) + MSBuildPostProcessOutputFile=$(_AssemblyPostProcessorMSBuildOutputFile) MtouchFloat32=$(MtouchFloat32) NoWarn=$(_BundlerNoWarn) Optimize=$(_BundlerOptimize) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 0edeaf326381..b940d445fb64 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,16 +6,16 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26356.110 - 10.0.0-beta.26356.110 + 10.0.0-beta.26357.110 + 10.0.0-beta.26357.110 0.11.5-alpha.26070.104 - 10.0.0-beta.26356.110 + 10.0.0-beta.26357.110 10.0.3-servicing.26070.104 10.0.3 10.0.3 - 10.0.400-preview.0.26356.110 + 10.0.400-preview.0.26357.110 10.0.3 - 10.0.400-preview.26356.110 + 10.0.400-preview.26357.110 26.0.11017 18.5.9227 @@ -30,7 +30,7 @@ This file should be imported by eng/Versions.props 18.5.9227 26.5.9004 - 11.0.0-prerelease.26353.1 + 11.0.0-prerelease.26357.1 18.0.9617 18.0.9617 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ab891f2fbbee..5d2ae04cb01c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,8 +1,8 @@ - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 https://github.com/dotnet/dotnet @@ -95,25 +95,25 @@ - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 - + https://github.com/dotnet/xharness - 4da0015e9dd1ed7d3992db7d1eb6878a5ee11ab2 + b631e17803318d67046871027986a005f3db02bf - + https://github.com/dotnet/dotnet - 0e603833cc19f22076e5fcac787ccd22f3900a51 + 2bd0e65f2efd50d56ae2fa3adfc1c3f440c1d223 diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml index 4eed0312b807..694f55a926e6 100644 --- a/eng/common/core-templates/steps/publish-logs.yml +++ b/eng/common/core-templates/steps/publish-logs.yml @@ -33,9 +33,6 @@ steps: '$(publishing-dnceng-devdiv-code-r-build-re)' '$(dn-bot-all-orgs-artifact-feeds-rw)' '$(akams-client-id)' - '$(microsoft-symbol-server-pat)' - '$(symweb-symbol-server-pat)' - '$(dnceng-symbol-server-pat)' '$(dn-bot-all-orgs-build-rw-code-rw)' '$(System.AccessToken)' ${{parameters.CustomSensitiveDataList}} diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml index e9a694afa58e..6e7666b4dcf8 100644 --- a/eng/common/core-templates/steps/source-index-stage1-publish.yml +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -14,8 +14,8 @@ steps: workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 8abfb71f7275..3150ccac6fcd 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -5,10 +5,11 @@ set -e usage() { echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]" - echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86" + echo "BuildArch can be: arm(default), arm64, loongarch64, ppc64le, riscv64, s390x, x64, x86" echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" + echo " for OpenBSD can be: openbsd" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" @@ -27,6 +28,8 @@ __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 +__OpenBSDArch=arm +__OpenBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm @@ -72,8 +75,8 @@ __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" -__FreeBSDBase="13.4-RELEASE" -__FreeBSDPkg="1.21.3" +__FreeBSDBase="13.5-RELEASE" +__FreeBSDPkg="2.7.5" __FreeBSDABI="13" __FreeBSDPackages="libunwind" __FreeBSDPackages+=" icu" @@ -82,6 +85,13 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" +__OpenBSDVersion="7.8" +__OpenBSDPackages="heimdal-libs" +__OpenBSDPackages+=" icu4c" +__OpenBSDPackages+=" libinotify" +__OpenBSDPackages+=" openssl" +__OpenBSDPackages+=" e2fsprogs" + __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" @@ -130,7 +140,6 @@ __AlpineKeys=' 616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ== 66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ== ' -__Keyring= __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 __SkipEmulation=0 @@ -153,6 +162,10 @@ while :; do __AlpineArch=armv7 __QEMUArch=arm ;; + armel) + # this is only used for tizen-build-rootfs.sh + __BuildArch=armel + ;; arm64) __BuildArch=arm64 __UbuntuArch=arm64 @@ -160,31 +173,8 @@ while :; do __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 - ;; - armel) - __BuildArch=armel - __UbuntuArch=armel - __UbuntuRepo="http://archive.debian.org/debian/" - __CodeName=buster - __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" - __LLDB_Package="liblldb-6.0-dev" - __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" - __UbuntuPackages="${__UbuntuPackages// libomp5/}" - __UbuntuSuites= - ;; - armv6) - __BuildArch=armv6 - __UbuntuArch=armhf - __QEMUArch=arm - __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/" - __CodeName=buster - __KeyringFile="/usr/share/keyrings/raspbian-archive-keyring.gpg" - __LLDB_Package="liblldb-6.0-dev" - __UbuntuSuites= - - if [[ -e "$__KeyringFile" ]]; then - __Keyring="--keyring $__KeyringFile" - fi + __OpenBSDArch=arm64 + __OpenBSDMachineArch=aarch64 ;; loongarch64) __BuildArch=loongarch64 @@ -193,10 +183,6 @@ while :; do __UbuntuArch=loong64 __UbuntuSuites=unreleased __LLDB_Package="liblldb-19-dev" - - if [[ "$__CodeName" == "sid" ]]; then - __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" - fi ;; riscv64) __BuildArch=riscv64 @@ -212,7 +198,7 @@ while :; do __AlpineArch=ppc64le __QEMUArch=ppc64le __UbuntuArch=ppc64el - __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" @@ -223,7 +209,7 @@ while :; do __AlpineArch=s390x __QEMUArch=s390x __UbuntuArch=s390x - __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/" __UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp-dev/}" __UbuntuPackages="${__UbuntuPackages// libomp5/}" @@ -235,15 +221,17 @@ while :; do __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 + __OpenBSDArch=amd64 + __OpenBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 - __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + __UbuntuRepo="https://archive.ubuntu.com/ubuntu/" ;; x86) __BuildArch=x86 __UbuntuArch=i386 __AlpineArch=x86 - __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + __UbuntuRepo="https://archive.ubuntu.com/ubuntu/" ;; lldb*) version="$(echo "$lowerI" | tr -d '[:alpha:]-=')" @@ -295,9 +283,7 @@ while :; do ;; noble) # Ubuntu 24.04 __CodeName=noble - if [[ -z "$__LLDB_Package" ]]; then - __LLDB_Package="liblldb-19-dev" - fi + __LLDB_Package="liblldb-19-dev" ;; stretch) # Debian 9 __CodeName=stretch @@ -305,7 +291,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://archive.debian.org/debian/" fi ;; buster) # Debian 10 @@ -314,7 +300,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://archive.debian.org/debian/" + __UbuntuRepo="https://archive.debian.org/debian/" fi ;; bullseye) # Debian 11 @@ -322,7 +308,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; bookworm) # Debian 12 @@ -330,7 +316,7 @@ while :; do __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; sid) # Debian sid @@ -339,25 +325,21 @@ while :; do # Debian-Ports architectures need different values case "$__UbuntuArch" in - amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x) + amd64|arm64|armhf|i386|mips64el|ppc64el|riscv64|s390x) __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.debian.org/debian/" + __UbuntuRepo="https://ftp.debian.org/debian/" fi ;; *) __KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/" + __UbuntuRepo="https://ftp.debian.org/debian-ports/" fi ;; esac - - if [[ -e "$__KeyringFile" ]]; then - __Keyring="--keyring $__KeyringFile" - fi ;; tizen) __CodeName= @@ -383,10 +365,14 @@ while :; do ;; freebsd14) __CodeName=freebsd - __FreeBSDBase="14.2-RELEASE" + __FreeBSDBase="14.3-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; + openbsd) + __CodeName=openbsd + __SkipUnmount=1 + ;; illumos) __CodeName=illumos __SkipUnmount=1 @@ -457,7 +443,7 @@ fi __UbuntuPackages+=" ${__LLDB_Package:-}" if [[ -z "$__UbuntuRepo" ]]; then - __UbuntuRepo="http://ports.ubuntu.com/" + __UbuntuRepo="https://ports.ubuntu.com/" fi if [[ -n "$__LLVM_MajorVersion" ]]; then @@ -544,15 +530,15 @@ if [[ "$__CodeName" == "alpine" ]]; then # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then # shellcheck disable=SC2086 __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi @@ -560,8 +546,8 @@ if [[ "$__CodeName" == "alpine" ]]; then # install all packages in one go # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages @@ -578,7 +564,7 @@ elif [[ "$__CodeName" == "freebsd" ]]; then curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version fi echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf - echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf + echo "FreeBSD: { url: \"pkg+https://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf mkdir -p "$__RootfsDir"/tmp # get and build package manager if [[ "$__hasWget" == 1 ]]; then @@ -592,9 +578,69 @@ elif [[ "$__CodeName" == "freebsd" ]]; then ./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}" # install packages we need. - INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) IGNORE_OSVERSION=yes "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "openbsd" ]]; then + # determine mirrors + OPENBSD_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/$__OpenBSDVersion/$__OpenBSDMachineArch" + + # download base system sets + ensureDownloadTool + + BASE_SETS=(base comp) + for set in "${BASE_SETS[@]}"; do + FILE="${set}${__OpenBSDVersion//./}.tgz" + echo "Downloading $FILE..." + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + else + curl -SL "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + fi + done + + PKG_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/${__OpenBSDVersion}/packages/${__OpenBSDMachineArch}" + + echo "Installing packages into sysroot..." + + # Fetch package index once + if [[ "$__hasWget" == 1 ]]; then + PKG_INDEX=$(wget -qO- "$PKG_MIRROR/") + else + PKG_INDEX=$(curl -s "$PKG_MIRROR/") + fi + + for pkg in $__OpenBSDPackages; do + PKG_FILE=$(echo "$PKG_INDEX" | grep -Po ">\K${pkg}-[0-9][^\" ]*\.tgz" \ + | sort -V | tail -n1) + + echo "Resolved package filename for $pkg: $PKG_FILE" + + [[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; } + + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf - + else + curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf - + fi + done + + echo "Creating versionless symlinks for shared libraries..." + # Find all versioned .so files and create the base .so symlink + for lib in "$__RootfsDir"/usr/lib/lib*.so.*; do + if [ -f "$lib" ]; then + # Extract the filename (e.g., libc++.so.12.0) + VERSIONED_NAME=$(basename "$lib") + # Remove the trailing version numbers (e.g., libc++.so) + BASE_NAME=${VERSIONED_NAME%.so.*}.so + # Create the symlink in the same directory + ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME" + fi + done + + echo "Cleaning up unnecessary paths" + # we don't use executables and kernel in rootfs (as we use host's compiler with -sysroot) + rm -rf "$__RootfsDir/usr/share" "$__RootfsDir/usr/bin" elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" @@ -759,6 +805,14 @@ elif [[ "$__CodeName" == "haiku" ]]; then elif [[ -n "$__CodeName" ]]; then __Suites="$__CodeName $(for suite in $__UbuntuSuites; do echo -n "$__CodeName-$suite "; done)" + __SigCheckArgs= + if [[ "$__SkipSigCheck" == "0" ]]; then + if [[ -e "$__KeyringFile" ]]; then + __SigCheckArgs="--keyring $__KeyringFile" + fi + __SigCheckArgs="$__SigCheckArgs --force-check-gpg" + fi + if [[ "$__SkipEmulation" == "1" ]]; then if [[ -z "$AR" ]]; then if command -v ar &>/dev/null; then @@ -774,31 +828,23 @@ elif [[ -n "$__CodeName" ]]; then PYTHON=${PYTHON_EXECUTABLE:-python3} # shellcheck disable=SC2086,SC2046 - echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + echo running "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages # shellcheck disable=SC2086,SC2046 - "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ + "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \ $(for suite in $__Suites; do echo -n "--suite $suite "; done) \ $__UbuntuPackages exit 0 fi - __UpdateOptions= - if [[ "$__SkipSigCheck" == "0" ]]; then - __Keyring="$__Keyring --force-check-gpg" - else - __Keyring= - __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" - fi - # shellcheck disable=SC2086 - echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" + echo running debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo" # shellcheck disable=SC2086 - if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then + if ! debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then echo "debootstrap failed! dumping debootstrap.log" cat "$__RootfsDir/debootstrap/debootstrap.log" exit 1 @@ -816,6 +862,11 @@ Components: main universe Signed-By: $__KeyringFile EOF + __UpdateOptions= + if [[ "$__SkipSigCheck" == "1" ]]; then + __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories" + fi + # shellcheck disable=SC2086 chroot "$__RootfsDir" apt-get update $__UpdateOptions chroot "$__RootfsDir" apt-get -f -y install diff --git a/eng/common/cross/install-debs.py b/eng/common/cross/install-debs.py index c81eb37e522e..1d1dfabf7dc9 100644 --- a/eng/common/cross/install-debs.py +++ b/eng/common/cross/install-debs.py @@ -4,6 +4,7 @@ import asyncio import aiohttp import gzip +import hashlib import os import re import shutil @@ -16,7 +17,7 @@ from collections import deque from functools import cmp_to_key -async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60): +async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60, checksum=None): """Asynchronous file download with retries.""" attempt = 0 while attempt < max_retries: @@ -25,19 +26,25 @@ async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, t if response.status == 200: with open(dest_path, "wb") as f: content = await response.read() + + # verify checksum if provided + if checksum: + sha256 = hashlib.sha256(content).hexdigest() + if sha256 != checksum: + raise Exception(f"SHA256 mismatch for {url}: expected {checksum}, got {sha256}") + f.write(content) print(f"Downloaded {url} at {dest_path}") return else: - print(f"Failed to download {url}, Status Code: {response.status}") - break + raise Exception(f"Failed to download {url}, Status Code: {response.status}") except (asyncio.CancelledError, asyncio.TimeoutError, aiohttp.ClientError) as e: print(f"Error downloading {url}: {type(e).__name__} - {e}. Retrying...") attempt += 1 await asyncio.sleep(retry_delay) - print(f"Failed to download {url} after {max_retries} attempts.") + raise Exception(f"Failed to download {url} after {max_retries} attempts.") async def download_deb_files_parallel(mirror, packages, tmp_dir): """Download .deb files in parallel.""" @@ -51,11 +58,11 @@ async def download_deb_files_parallel(mirror, packages, tmp_dir): if filename: url = f"{mirror}/{filename}" dest_path = os.path.join(tmp_dir, os.path.basename(filename)) - tasks.append(asyncio.create_task(download_file(session, url, dest_path))) + tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get("SHA256")))) await asyncio.gather(*tasks) -async def download_package_index_parallel(mirror, arch, suites): +async def download_package_index_parallel(mirror, arch, suites, check_sig, keyring): """Download package index files for specified suites and components entirely in memory.""" tasks = [] timeout = aiohttp.ClientTimeout(total=60) @@ -63,10 +70,9 @@ async def download_package_index_parallel(mirror, arch, suites): async with aiohttp.ClientSession(timeout=timeout) as session: for suite in suites: for component in ["main", "universe"]: - url = f"{mirror}/dists/{suite}/{component}/binary-{arch}/Packages.gz" - tasks.append(fetch_and_decompress(session, url)) + tasks.append(fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring)) - results = await asyncio.gather(*tasks, return_exceptions=True) + results = await asyncio.gather(*tasks) merged_content = "" for result in results: @@ -77,20 +83,75 @@ async def download_package_index_parallel(mirror, arch, suites): return merged_content -async def fetch_and_decompress(session, url): +async def fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring): """Fetch and decompress the Packages.gz file.""" - try: - async with session.get(url) as response: - if response.status == 200: - compressed_data = await response.read() - decompressed_data = gzip.decompress(compressed_data).decode('utf-8') - print(f"Downloaded index: {url}") - return decompressed_data - else: - print(f"Skipped index: {url} (doesn't exist)") - return None - except Exception as e: - print(f"Error fetching {url}: {e}") + + path = f"{component}/binary-{arch}/Packages.gz" + url = f"{mirror}/dists/{suite}/{path}" + + async with session.get(url) as response: + if response.status == 200: + compressed_data = await response.read() + decompressed_data = gzip.decompress(compressed_data).decode('utf-8') + print(f"Downloaded index: {url}") + + if check_sig: + # Verify the package index against the sha256 recorded in the Release file + release_file_content = await fetch_release_file(session, mirror, suite, keyring) + packages_sha = parse_release_file(release_file_content, path) + + sha256 = hashlib.sha256(compressed_data).hexdigest() + if sha256 != packages_sha: + raise Exception(f"SHA256 mismatch for {path}: expected {packages_sha}, got {sha256}") + print(f"Checksum verified for {path}") + + return decompressed_data + else: + print(f"Skipped index: {url} (doesn't exist)") + return None + +async def fetch_release_file(session, mirror, suite, keyring): + """Fetch Release and Release.gpg files and verify the signature.""" + + release_url = f"{mirror}/dists/{suite}/Release" + release_gpg_url = f"{mirror}/dists/{suite}/Release.gpg" + + with tempfile.NamedTemporaryFile() as release_file, tempfile.NamedTemporaryFile() as release_gpg_file: + await download_file(session, release_url, release_file.name) + await download_file(session, release_gpg_url, release_gpg_file.name) + + print("Verifying signature of Release with Release.gpg.") + # Use gpgv rather than gpg for verification. gpgv verifies a detached + # signature against a fixed keyring without involving gpg-agent or + # keyboxd, which makes it robust on hosts running GnuPG 2.4+ (e.g. Azure + # Linux) where "gpg --keyring" routes through keyboxd and can fail. + verify_command = ["gpgv"] + if keyring: + verify_command += ["--keyring", keyring] + verify_command += [release_gpg_file.name, release_file.name] + result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if result.returncode != 0: + raise Exception(f"Signature verification failed: {result.stderr.decode('utf-8')}") + + print("Signature verified successfully.") + + with open(release_file.name) as f: + return f.read() + +def parse_release_file(content, path): + """Parses the Release file and returns sha256 checksum of the specified path.""" + + # data looks like this: + # + matches = re.findall(r'^ (\S*) +(\S*) +(\S*)$', content, re.MULTILINE) + + for entry in matches: + # the file has both md5 and sha256 checksums, we want sha256 which has a length of 64 + if entry[2] == path and len(entry[0]) == 64: + return entry[0] + + raise Exception(f"Could not find checksum for {path} in Release file.") def parse_debian_version(version): """Parse a Debian package version into epoch, upstream version, and revision.""" @@ -171,13 +232,15 @@ def parse_package_index(content): filename = fields.get("Filename") depends = fields.get("Depends") provides = fields.get("Provides", None) + sha256 = fields.get("SHA256") # Only update if package_name is not in packages or if the new version is higher if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0: packages[package_name] = { "Version": version, "Filename": filename, - "Depends": depends + "Depends": depends, + "SHA256": sha256 } # Update aliases if package provides any alternatives @@ -233,7 +296,7 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): os.makedirs(extract_dir, exist_ok=True) with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir: - result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = subprocess.run([ar_tool, "t", os.path.abspath(deb_file)], cwd=tmp_subdir, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) tar_filename = None for line in result.stdout.decode().splitlines(): @@ -247,7 +310,8 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): tar_file_path = os.path.join(tmp_subdir, tar_filename) print(f"Extracting {tar_filename} from {deb_file}..") - subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True) + with open(tar_file_path, "wb") as outfile: + subprocess.run([ar_tool, "p", os.path.abspath(deb_file), tar_filename], check=True, stdout=outfile, stderr=subprocess.PIPE) file_extension = os.path.splitext(tar_file_path)[1].lower() @@ -268,7 +332,18 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool): raise ValueError(f"Unsupported compression format: {file_extension}") with tarfile.open(tar_file_path, mode) as tar: - tar.extractall(path=extract_dir, filter='fully_trusted') + tar.extractall(path=extract_dir, filter=_rootfs_extraction_filter) + +def _rootfs_extraction_filter(member, dest_path): + """Tarfile extraction filter based on the 'data' filter that additionally + rewrites absolute-target symlinks/hardlinks into rootfs-relative paths. + """ + if (member.issym() or member.islnk()) and os.path.isabs(member.linkname): + link_dir = os.path.dirname(member.name) + new_linkname = os.path.relpath(member.linkname.lstrip('/'), + start=link_dir or '.') + member = member.replace(linkname=new_linkname, deep=False) + return tarfile.data_filter(member, dest_path) def finalize_setup(rootfsdir): lib_dir = os.path.join(rootfsdir, 'lib') @@ -295,24 +370,17 @@ def finalize_setup(rootfsdir): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS") - parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)") parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)") parser.add_argument("--rootfsdir", required=True, help="Destination directory.") parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.') - parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") + parser.add_argument("--mirror", required=True, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)") parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)") + parser.add_argument("--force-check-gpg", required=False, action='store_true', help="Verify the packages against signatures in Release file.") + parser.add_argument("--keyring", required=False, default='', help="Keyring file to check signature of Release file.") parser.add_argument("packages", nargs="+", help="List of package names to be installed.") args = parser.parse_args() - if args.mirror is None: - if args.distro == "ubuntu": - args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports" - elif args.distro == "debian": - args.mirror = "http://ftp.debian.org/debian-ports" - else: - raise Exception("Unsupported distro") - DESIRED_PACKAGES = args.packages + [ # base packages "dpkg", "busybox", @@ -322,9 +390,16 @@ def finalize_setup(rootfsdir): "debianutils" ] - print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") + print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}") + + check_sig = args.force_check_gpg + if check_sig and not args.keyring: + print("ERROR: --force-check-gpg requires --keyring to specify a keyring file for signature verification.") + print("Install the appropriate keyring package (e.g., debian-ports-archive-keyring, ubuntu-archive-keyring)") + print("or pass --skipsigcheck to build-rootfs.sh to disable signature checking.") + sys.exit(1) - package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite)) + package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite, check_sig, args.keyring)) packages_info, aliases = parse_package_index(package_index_content) diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 0ff85cf0367e..f65c689f6954 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -3,15 +3,22 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) +unset(OPENBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) + +file(GLOB OPENBSD_PROBE "${CROSS_ROOTFS}/etc/signify/openbsd-*.pub") + if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) +elseif(OPENBSD_PROBE) + set(CMAKE_SYSTEM_NAME OpenBSD) + set(OPENBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) @@ -52,7 +59,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu") endif() elseif(FREEBSD) - set(triple "aarch64-unknown-freebsd12") + set(TOOLCHAIN "aarch64-unknown-freebsd14") + elseif(OPENBSD) + set(TOOLCHAIN "aarch64-unknown-openbsd") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) @@ -108,7 +117,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu") endif() elseif(FREEBSD) - set(triple "x86_64-unknown-freebsd12") + set(TOOLCHAIN "x86_64-unknown-freebsd14") + elseif(OPENBSD) + set(TOOLCHAIN "x86_64-unknown-openbsd") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) @@ -148,9 +159,6 @@ if(TIZEN) else() find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") endif() - - message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}") - include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++) include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN}) endif() @@ -193,11 +201,11 @@ if(ANDROID) # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) -elseif(FREEBSD) +elseif(FREEBSD OR OPENBSD) # we cross-compile by instructing clang - set(CMAKE_C_COMPILER_TARGET ${triple}) - set(CMAKE_CXX_COMPILER_TARGET ${triple}) - set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld") @@ -214,13 +222,19 @@ elseif(ILLUMOS) locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) elseif(HAIKU) set(CMAKE_SYSROOT "${CROSS_ROOTFS}") - set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") - locate_toolchain_exec(gcc CMAKE_C_COMPILER) - locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + if ("$ENV{CCC_CC}" MATCHES ".*gcc.*") + set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin") + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + else() + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64") + endif() # let CMake set up the correct search paths include(Platform/Haiku) @@ -291,7 +305,7 @@ endif() # Specify compile options -if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD AND NOT OPENBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index e33743105635..a5be41db6906 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -29,11 +29,11 @@ function InstallDarcCli ($darcVersion, $toolpath) { Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g }else { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index e889f439b8dc..e6ba4ee28c11 100644 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -73,9 +73,9 @@ function InstallDarcCli { echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g) else - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } diff --git a/eng/common/native/LocateNativeCompiler.targets b/eng/common/native/LocateNativeCompiler.targets new file mode 100644 index 000000000000..028b33d94441 --- /dev/null +++ b/eng/common/native/LocateNativeCompiler.targets @@ -0,0 +1,27 @@ + + + + + clang + $(ROOTFS_DIR) + + + + + + + + $(_CC_LDFLAGS.SubString(0, $(_CC_LDFLAGS.IndexOf(';')))) + <_LDFLAGS>$(_CC_LDFLAGS.SubString($([MSBuild]::Add($(_CC_LDFLAGS.IndexOf(';')), 1)))) + lld + + + diff --git a/eng/common/native/NativeAotSupported.props b/eng/common/native/NativeAotSupported.props new file mode 100644 index 000000000000..559a6663929e --- /dev/null +++ b/eng/common/native/NativeAotSupported.props @@ -0,0 +1,26 @@ + + + + + <_NativeAotSupportedOS Condition=" + '$(TargetOS)' != 'browser' and + '$(TargetOS)' != 'haiku' and + '$(TargetOS)' != 'illumos' and + '$(TargetOS)' != 'netbsd' and + '$(TargetOS)' != 'solaris' + ">true + + + <_NativeAotSupportedArch Condition=" + '$(TargetArchitecture)' != 'wasm' and + ('$(TargetArchitecture)' != 'x86' or '$(TargetOS)' == 'windows') + ">true + + true + + + diff --git a/eng/common/native/init-distro-rid.sh b/eng/common/native/init-distro-rid.sh index 83ea7aab0e08..8fc6d2fec78d 100644 --- a/eng/common/native/init-distro-rid.sh +++ b/eng/common/native/init-distro-rid.sh @@ -39,6 +39,8 @@ getNonPortableDistroRid() # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" + elif [ "$targetOs" = "openbsd" ]; then + nonPortableRid="openbsd.$(uname -r)-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 477a44f335be..aff839fa0974 100644 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -24,14 +24,16 @@ case "$os" in apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ - libssl-dev libkrb5-dev pigz cpio + libssl-dev libkrb5-dev pigz cpio ninja-build file localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" - $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file + elif [ "$ID" = "amzn" ]; then + dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file elif [ "$ID" = "alpine" ]; then - apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio + apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja file else echo "Unsupported distro. distro: $ID" exit 1 @@ -52,6 +54,7 @@ brew "openssl@3" brew "pkgconf" brew "python3" brew "pigz" +brew "ninja" EOF ;; diff --git a/eng/common/post-build/redact-logs.ps1 b/eng/common/post-build/redact-logs.ps1 index 472d5bb562c9..c1e4104b79a4 100644 --- a/eng/common/post-build/redact-logs.ps1 +++ b/eng/common/post-build/redact-logs.ps1 @@ -48,8 +48,8 @@ try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest - Write-Host "'$dotnet' tool install $packageName --local --add-source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" - & "$dotnet" tool install $packageName --local --add-source "$PackageFeed" -v $verbosity --version $BinlogToolVersion + Write-Host "'$dotnet' tool install $packageName --local --source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" + & "$dotnet" tool install $packageName --local --source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath diff --git a/eng/common/templates/steps/vmr-sync.yml b/eng/common/templates/steps/vmr-sync.yml index eb619c502683..cdc6a28ff1f6 100644 --- a/eng/common/templates/steps/vmr-sync.yml +++ b/eng/common/templates/steps/vmr-sync.yml @@ -45,11 +45,11 @@ steps: workingDirectory: ${{ parameters.vmrPath }} - script: | - ./eng/common/vmr-sync.sh \ - --vmr ${{ parameters.vmrPath }} \ - --tmp $(Agent.TempDirectory) \ - --azdev-pat '$(dn-bot-all-orgs-code-r)' \ - --ci \ + ./eng/common/vmr-sync.sh \ + --vmr ${{ parameters.vmrPath }} \ + --tmp $(Agent.TempDirectory) \ + --azdev-pat '$(AzdoToken)' \ + --ci \ --debug if [ "$?" -ne 0 ]; then @@ -67,11 +67,11 @@ steps: condition: eq(variables['Agent.OS'], 'Windows_NT') - powershell: | - ./eng/common/vmr-sync.ps1 ` - -vmr ${{ parameters.vmrPath }} ` - -tmp $(Agent.TempDirectory) ` - -azdevPat '$(dn-bot-all-orgs-code-r)' ` - -ci ` + ./eng/common/vmr-sync.ps1 ` + -vmr ${{ parameters.vmrPath }} ` + -tmp $(Agent.TempDirectory) ` + -azdevPat '$(AzdoToken)' ` + -ci ` -debugOutput if ($LASTEXITCODE -ne 0) { diff --git a/eng/common/templates/vmr-build-pr.yml b/eng/common/templates/vmr-build-pr.yml index 2f3694fa1323..d24de935248a 100644 --- a/eng/common/templates/vmr-build-pr.yml +++ b/eng/common/templates/vmr-build-pr.yml @@ -33,7 +33,7 @@ resources: - repository: vmr type: github name: dotnet/dotnet - endpoint: dotnet + endpoint: public ref: refs/heads/main # Set to whatever VMR branch the PR build should insert into stages: diff --git a/global.json b/global.json index 0b4ff331337e..32005f79bfa6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.400-preview.0.26356.110", + "version": "10.0.400-preview.0.26357.110", "paths": [ "builds/downloads/dotnet", "$host$" @@ -8,9 +8,9 @@ "errorMessage": "The .NET SDK could not be found, please run 'make dotnet -C builds'." }, "tools": { - "dotnet": "10.0.400-preview.0.26356.110" + "dotnet": "10.0.400-preview.0.26357.110" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26356.110" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26357.110" } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs index 9a94d2bb4cb8..fb9e61e6d893 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs @@ -75,6 +75,13 @@ public override bool Execute () rv = preparer.Prepare (out exceptions); } + var totalDuration = TimeSpan.Zero; + foreach (var step in preparer.StepExecutions) { + totalDuration += step.Duration; + Log.LogMessage (MessageImportance.Low, $"{step.Duration.ToString (@"hh\:mm\:ss\.fffffff")} {step.Name}: {(step.ModifiedAssemblies ? " ✏️ modified one or more assemblies" : " ✅ did not modify any assemblies")}"); + } + Log.LogMessage (MessageImportance.Low, $"{totalDuration.ToString (@"hh\:mm\:ss\.fffffff")} Total for all steps"); + foreach (var pe in exceptions) { if (pe.IsError (this)) { ((IToolLog) this).LogError (pe); diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index edb94b8b57b2..413442c5494a 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -3502,7 +3502,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. Name="_PrepareAssemblies" Condition="'$(PrepareAssemblies)' == 'true'" Inputs="@(_AssembliesToPrepare)" - Outputs="@(_AssembliesToPrepare->'$(PreparedAssembliesDirectory)%(Filename)%(Extension)')" + Outputs="@(_AssembliesToPrepare->'$(PreparedAssembliesDirectory)%(Filename)%(Extension)');$(_AssemblyPreparerMSBuildOutputFile)" DependsOnTargets="_PrepareAssembliesForPreparation;_ComputeAssembliesToPostprocessOnPublish;SelectRegistrar" > + @@ -3565,6 +3566,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + diff --git a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets index 9a07dc40030d..0341c8a3cf17 100644 --- a/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets +++ b/msbuild/Xamarin.iOS.Tasks.Windows/Xamarin.Messaging.targets @@ -71,7 +71,7 @@ - + { + if (e.Data is not null) + lock (writer) + writer.WriteLine (e.Data); + }; + logStreamProcess.ErrorDataReceived += (_, e) => { + if (e.Data is not null) + lock (writer) + writer.WriteLine (e.Data); + }; + logStreamProcess.Start (); + logStreamProcess.BeginOutputReadLine (); + logStreamProcess.BeginErrorReadLine (); + + Console.WriteLine ($"Started 'log stream' (pid {logStreamProcess.Id}), writing to {logStreamFile}"); +} + var isAppleSilicon = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 || Environment.GetEnvironmentVariable ("PROCESSOR_ARCHITECTURE")?.Contains ("ARM", StringComparison.OrdinalIgnoreCase) == true; @@ -267,6 +302,34 @@ Console.WriteLine ($"HTML report written to {htmlReportPath}"); } +// Stop 'log stream' and zip the output +if (logStreamProcess is not null && logStreamFile is not null) { + try { + NativeMethods.kill (logStreamProcess.Id, 2 /* SIGINT */); + logStreamProcess.WaitForExit (10_000); + } catch { + // Process may have already exited + } + + // Flush and close the log writer + logStreamWriter?.Dispose (); + + try { + Console.WriteLine ($"Wrote {new FileInfo (logStreamFile).Length} bytes to {logStreamFile}"); + + // Zip the log file + var zipPath = logStreamFile + ".zip"; + using (var zip = ZipFile.Open (zipPath, ZipArchiveMode.Create)) + zip.CreateEntryFromFile (logStreamFile, Path.GetFileName (logStreamFile), CompressionLevel.Optimal); + File.Delete (logStreamFile); + Console.WriteLine ($"Compressed log stream to {zipPath}"); + } catch (Exception ex) { + Console.Error.WriteLine ($"Warning: Failed to save log stream output: {ex.Message}"); + } + + logStreamProcess.Dispose (); +} + return failedSuites > 0 ? 1 : 0; // ===== Helper methods ===== diff --git a/src/Foundation/ModelAttribute.cs b/src/Foundation/ModelAttribute.cs index f90e249bb2f0..78465d73b284 100644 --- a/src/Foundation/ModelAttribute.cs +++ b/src/Foundation/ModelAttribute.cs @@ -25,19 +25,23 @@ namespace Foundation { - /// + /// Flags a class or interface as a model, indicating that only explicitly overridden methods are exposed to the Objective-C runtime. + /// + /// Objective-C protocols are like interfaces, but they support optional methods, that is, not all of the methods need to be implemented for the protocol to work. + /// The [Model] attribute is applied to a class that can have optional methods, and it is typically used for declaring Objective-C delegates or data models that have a number of optional methods. The runtime treats classes with the Model attribute applied specially: only when a user overrides methods in a class is the actual override exposed to the Objective-C world as existing. + /// This attribute is used in all of the models and delegate classes to allow the user to only implement the methods that they are interested in. + /// [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface)] public sealed class ModelAttribute : Attribute { - /// Default constructor. - /// To be added. + /// Initializes a new instance of the class. public ModelAttribute () { } - /// Specifies if the Objective-C type name for the model. - /// The Objective-C type name for the model. - /// - /// This value is only taken into account when used in binding code / projects. - /// + /// Gets or sets the Objective-C type name for the model. + /// The Objective-C type name for the model. + /// + /// This value is only taken into account when used in binding code / projects. + /// public string? Name { get; set; } } } diff --git a/src/ILLink.Substitutions.MacCatalyst.xml b/src/ILLink.Substitutions.MacCatalyst.xml index da45aabaf46c..6cf57b701e0a 100644 --- a/src/ILLink.Substitutions.MacCatalyst.xml +++ b/src/ILLink.Substitutions.MacCatalyst.xml @@ -10,6 +10,8 @@ + + diff --git a/src/ILLink.Substitutions.iOS.xml b/src/ILLink.Substitutions.iOS.xml index 3436035d8aff..e778b006756d 100644 --- a/src/ILLink.Substitutions.iOS.xml +++ b/src/ILLink.Substitutions.iOS.xml @@ -12,6 +12,8 @@ + + diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml index d93fac786837..3c1ddefda20d 100644 --- a/src/ILLink.Substitutions.macOS.xml +++ b/src/ILLink.Substitutions.macOS.xml @@ -9,6 +9,8 @@ + + diff --git a/src/ILLink.Substitutions.tvOS.xml b/src/ILLink.Substitutions.tvOS.xml index 7131bcf39363..4ea0b88f2fe3 100644 --- a/src/ILLink.Substitutions.tvOS.xml +++ b/src/ILLink.Substitutions.tvOS.xml @@ -12,6 +12,8 @@ + + diff --git a/src/StoreKit/Enums.cs b/src/StoreKit/Enums.cs index 9c1169aa7b45..eb5058aa6475 100644 --- a/src/StoreKit/Enums.cs +++ b/src/StoreKit/Enums.cs @@ -49,20 +49,20 @@ public enum SKError : long { /// Indicates that the cloud service network connection failed. CloudServiceNetworkConnectionFailed, // iOS 10.3 - /// To be added. + /// Indicates that the cloud service has been revoked. CloudServiceRevoked, // iOS 12.2 - /// To be added. + /// Indicates that a privacy acknowledgement is required before proceeding. PrivacyAcknowledgementRequired, - /// To be added. + /// Indicates that the request data is unauthorized. UnauthorizedRequestData, - /// To be added. + /// Indicates that the offer identifier is invalid. InvalidOfferIdentifier, - /// To be added. + /// Indicates that the signature is invalid. InvalidSignature, - /// To be added. + /// Indicates that required offer parameters are missing. MissingOfferParams, - /// To be added. + /// Indicates that the offer price is invalid. InvalidOfferPrice, OverlayCancelled = 15, diff --git a/tests/common/shared-dotnet.mk b/tests/common/shared-dotnet.mk index 21f5f72ba6fe..c8448bdb0225 100644 --- a/tests/common/shared-dotnet.mk +++ b/tests/common/shared-dotnet.mk @@ -150,8 +150,12 @@ else ifeq ($(PLATFORM),tvOS) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-tvos/$(PATH_RID)$(TESTNAME).app/$(TESTNAME)" else ifeq ($(PLATFORM),MacCatalyst) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-maccatalyst/$(PATH_RID)$(TESTNAME).app/Contents/MacOS/$(TESTNAME)" +APPMANIFEST:=$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-maccatalyst/$(PATH_RID)$(TESTNAME).app/Contents/Info.plist +BUNDLE_ID=$(shell defaults read "$(APPMANIFEST)" CFBundleIdentifier 2>/dev/null) else ifeq ($(PLATFORM),macOS) EXECUTABLE="$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-macos/$(PATH_RID)$(TESTNAME).app/Contents/MacOS/$(TESTNAME)" +APPMANIFEST:=$(abspath .)/bin/$(CONFIG)/$(TEST_TFM)-macos/$(PATH_RID)$(TESTNAME).app/Contents/Info.plist +BUNDLE_ID=$(shell defaults read "$(APPMANIFEST)" CFBundleIdentifier 2>/dev/null) else EXECUTABLE="unknown-executable-platform-$(PLATFORM)" endif @@ -187,8 +191,15 @@ run: prepare @echo "Running $(wildcard *.?sproj)..." $(Q) $(DOTNET) build "/bl:$(abspath $@-$(BINLOG_TIMESTAMP).binlog)" *.?sproj $(DOTNET_BUILD_VERBOSITY) $(BUILD_ARGUMENTS) $(CONFIG_ARGUMENT) $(UNIVERSAL_ARGUMENT) $(NATIVEAOT_ARGUMENTS) $(TEST_VARIATION_ARGUMENT) -t:Run $(RUN_ARGUMENTS) -tl:off -run-bare: +# Delete the saved application state for the app, to prevent the +# "Do you want to try to reopen its windows again?" dialog from +# blocking automated runs after a crash. See https://github.com/dotnet/macios/issues/25922 +delete-saved-state: + $(Q) if test -n "$(BUNDLE_ID)"; then rm -rf "$(HOME)/Library/Saved Application State/$(BUNDLE_ID).savedState" && echo "Deleted saved state for $(BUNDLE_ID)"; fi + +run-bare: delete-saved-state $(Q) $(EXECUTABLE) --autostart --autoexit $(RUN_ARGUMENTS) + $(Q) $(MAKE) delete-saved-state # Get the list of applicable simulators, and pick the first in the list. # Make sure to have a matching simulator runtime installed, otherwise this won't work. diff --git a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs new file mode 100644 index 000000000000..3bf5c7cfe633 --- /dev/null +++ b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.StructuredLogger; + +#nullable enable + +namespace Xamarin.Tests { + [TestFixture] + public class DynamicRegistrationSupportedTest : TestBaseClass { + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", "true")] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64", "false")] + public void UserSpecifiedValue (ApplePlatform platform, string runtimeIdentifiers, string dynamicRegistrationSupported) + { + // When the user sets $(DynamicRegistrationSupported), the value must be passed straight through to + // the 'ObjCRuntime.Runtime.DynamicRegistrationSupported' trimmer feature switch (the assembly-preparer + // doesn't need to compute it, so RegistrarRemovalTrackingStep is skipped). + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = GetDefaultProperties (runtimeIdentifiers); + // DynamicRegistrationSupported maps to the 'remove-dynamic-registrar' optimization, which is only + // valid with a static registrar, so use one (managed-static) and enable trimming. + properties ["MtouchLink"] = "SdkOnly"; + properties ["Registrar"] = "managed-static"; + properties ["PrepareAssemblies"] = "true"; + properties ["PostProcessAssemblies"] = "true"; + properties ["DynamicRegistrationSupported"] = dynamicRegistrationSupported; + + var rv = DotNet.AssertBuild (project_path, properties); + + var featureSwitch = GetRuntimeHostConfigurationOption (rv.BinLogPath, "ObjCRuntime.Runtime.DynamicRegistrationSupported"); + Assert.That (featureSwitch, Is.Not.Null, "The DynamicRegistrationSupported feature switch must be set."); + Assert.That (featureSwitch?.GetMetadata ("Value"), Is.EqualTo (dynamicRegistrationSupported), "The feature switch value must match the user-specified value."); + } + + [Test] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64")] + public void SkippedWhenPlatformAssemblyNotTrimmed (ApplePlatform platform, string runtimeIdentifiers) + { + // When the platform assembly isn't being trimmed (link mode None), the dynamic registrar can't be + // removed, so RegistrarRemovalTrackingStep is skipped and no DynamicRegistrationSupported feature + // switch is emitted (the dynamic registrar is kept, which is the default). + var project = "MySimpleApp"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, platform: platform); + Clean (project_path); + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["MtouchLink"] = "None"; + properties ["PrepareAssemblies"] = "true"; + properties ["PostProcessAssemblies"] = "true"; + + var rv = DotNet.AssertBuild (project_path, properties); + + var featureSwitch = GetRuntimeHostConfigurationOption (rv.BinLogPath, "ObjCRuntime.Runtime.DynamicRegistrationSupported"); + Assert.That (featureSwitch, Is.Null, "No DynamicRegistrationSupported feature switch should be set when the platform assembly isn't trimmed."); + } + + // Returns the last RuntimeHostConfigurationOption item with the given name (ItemSpec) added during the build. + static ITaskItem? GetRuntimeHostConfigurationOption (string binLogPath, string name) + { + ITaskItem? rv = null; + foreach (var args in BinLog.ReadBuildEvents (binLogPath)) { + if (args is not TaskParameterEventArgs tpea) + continue; + if (tpea.Kind != TaskParameterMessageKind.AddItem) + continue; + if (tpea.ItemType != "RuntimeHostConfigurationOption") + continue; + foreach (var item in tpea.Items) { + if (item is ITaskItem taskItem && taskItem.ItemSpec == name) + rv = taskItem; + } + } + return rv; + } + } +} diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 193984cb94ac..1bce8afd73cf 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -2245,15 +2245,16 @@ public void PluralRuntimeIdentifiers (ApplePlatform platform, string runtimeIden PluralRuntimeIdentifiersImpl (platform, runtimeIdentifiers); } - internal static void PluralRuntimeIdentifiersImpl (ApplePlatform platform, string runtimeIdentifiers, Dictionary? extraProperties = null) + internal static void PluralRuntimeIdentifiersImpl (ApplePlatform platform, string runtimeIdentifiers, Dictionary? extraProperties = null, string configuration = "Debug") { var project = "MySimpleApp"; Configuration.IgnoreIfIgnoredPlatform (platform); Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); - var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: configuration); Clean (project_path); var properties = GetDefaultProperties (extraProperties: extraProperties); + properties ["Configuration"] = configuration; properties ["RuntimeIdentifiers"] = runtimeIdentifiers; DotNet.AssertBuild (project_path, properties); diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index fbe234b535a6..f04e046c1e2c 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -480,6 +480,8 @@ protected Execution Execute (string executable, out string output, out string ma if (!File.Exists (executable)) throw new FileNotFoundException ($"The executable '{executable}' does not exists."); + DeleteSavedState (executable); + magicWord = Guid.NewGuid ().ToString (); var env = new Dictionary { { "MAGIC_WORD", magicWord }, @@ -492,9 +494,55 @@ protected Execution Execute (string executable, out string output, out string ma var rv = Execution.RunAsync (executable, Array.Empty (), environment: env, timeout: TimeSpan.FromSeconds (30)).Result; output = rv.Output.MergedOutput; + + DeleteSavedState (executable); + return rv; } + // Delete the saved application state for the app being launched, to prevent + // the "Do you want to try to reopen its windows again?" dialog from showing + // if the app crashed during a previous test run. See https://github.com/dotnet/macios/issues/25922 + static void DeleteSavedState (string executable) + { + // Find the .app bundle directory from the executable path + var dir = Path.GetDirectoryName (executable); + while (!string.IsNullOrEmpty (dir) && !dir.EndsWith (".app", StringComparison.OrdinalIgnoreCase)) + dir = Path.GetDirectoryName (dir); + + if (string.IsNullOrEmpty (dir)) + return; + + // Read the bundle identifier from Info.plist + string? bundleIdentifier = null; + var infoPlistPath = Path.Combine (dir, "Contents", "Info.plist"); + if (!File.Exists (infoPlistPath)) + infoPlistPath = Path.Combine (dir, "Info.plist"); + if (!File.Exists (infoPlistPath)) + return; + + try { + var infoPlist = PDictionary.OpenFile (infoPlistPath); + bundleIdentifier = infoPlist.GetString ("CFBundleIdentifier")?.Value; + } catch (Exception e) { + Console.WriteLine ($"Could not read bundle identifier from '{infoPlistPath}': {e.Message}"); + return; + } + + if (string.IsNullOrEmpty (bundleIdentifier)) + return; + + var savedStateDir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", $"{bundleIdentifier}.savedState"); + try { + if (Directory.Exists (savedStateDir)) { + Directory.Delete (savedStateDir, true); + Console.WriteLine ($"Deleted saved application state: {savedStateDir}"); + } + } catch (Exception e) { + Console.WriteLine ($"Could not delete saved application state '{savedStateDir}': {e.Message}"); + } + } + public static StringBuilder AssertExecute (string executable, params string [] arguments) { return AssertExecute (executable, arguments, out _); diff --git a/tests/dotnet/UnitTests/WindowsTest.cs b/tests/dotnet/UnitTests/WindowsTest.cs index e156da865385..c6e887dfe56b 100644 --- a/tests/dotnet/UnitTests/WindowsTest.cs +++ b/tests/dotnet/UnitTests/WindowsTest.cs @@ -261,12 +261,13 @@ public void BundleStructureWithRemoteMac (ApplePlatform platform, string runtime } [Category ("RemoteWindows")] - [TestCase (ApplePlatform.iOS, "ios-arm64")] - [TestCase (ApplePlatform.iOS, "iossimulator-arm64;iossimulator-x64")] - public void PluralRuntimeIdentifiersWithRemoteMac (ApplePlatform platform, string runtimeIdentifiers) + [TestCase (ApplePlatform.iOS, "ios-arm64", "Debug")] + [TestCase (ApplePlatform.iOS, "ios-arm64", "Release")] + [TestCase (ApplePlatform.iOS, "iossimulator-arm64;iossimulator-x64", "Debug")] + public void PluralRuntimeIdentifiersWithRemoteMac (ApplePlatform platform, string runtimeIdentifiers, string configuration) { var properties = AddRemoteProperties (); - DotNetProjectTest.PluralRuntimeIdentifiersImpl (platform, runtimeIdentifiers, properties); + DotNetProjectTest.PluralRuntimeIdentifiersImpl (platform, runtimeIdentifiers, properties, configuration); } [Category ("RemoteWindows")] diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs index 53caadb1dd1d..d03b0cf31e4c 100644 --- a/tools/assembly-preparer/AssemblyPreparer.cs +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; using System.IO; using System.Runtime.Serialization; using Mono.Cecil; @@ -40,6 +41,12 @@ public string IntermediateOutputPath { public List Assemblies { get; set; } = new List (); + // The list of steps that were executed, along with how long each step took and whether it modified any assemblies. + public List StepExecutions { get; } = new List (); + + // Set to true (via the AppBundleRewriter.AssemblySaved callback) whenever the currently executing step modifies an assembly. + bool currentStepModifiedAssemblies; + public IList<(string Path, AssemblyDefinition Assembly, string? OriginatingAssembly)> AddedAssemblies => configuration.AddedAssemblies; LinkerConfiguration.Configurator GetConfigurator (string? reproPath = null, Func? assemblyPreparerInfoFactory = null) @@ -116,8 +123,8 @@ public static AssemblyPreparer LoadFromReproPath (string reproPath) public bool Prepare (out List exceptions) { - var steps = new ConfigurationAwareStep [] { - // All the same steps as the custom trimmer steps that are run before MarkStep in Xamarin.Shared.Sdk.targets (and in the same order). + // All the same steps as the custom trimmer steps that are run before MarkStep in Xamarin.Shared.Sdk.targets (and in the same order). + var steps = new List { // CollectAssembliesStep new LoadAssembliesStep (), new ComputeMethodOverridesStep (), @@ -131,14 +138,23 @@ public bool Prepare (out List exceptions) new MarkForStaticRegistrarStep (), new MarkNSObjectsStep (), new InlineDlfcnMethodsStep (), - new RegistrarRemovalTrackingStep (), - // PreMarkDispatcher: I don't think we need this one - new ManagedRegistrarStep (), - new TrimmableRegistrarStep (), - new ManagedRegistrarLookupTablesStep (), - new InlineClassGetHandleStep (), - new SaveAssembliesStep (), }; + + // Only add RegistrarRemovalTrackingStep if it's needed: + // * If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value (it's + // passed straight through to the trimmer feature switch). + // * If nothing is being trimmed, the dynamic registrar (which lives in the platform assembly, an SDK + // assembly that's only trimmed when trimming is enabled) can't be removed, so there's nothing to compute. + if (!configuration.DynamicRegistrationSupported.HasValue && configuration.Application.AreAnyAssembliesTrimmed) + steps.Add (new RegistrarRemovalTrackingStep ()); + + // PreMarkDispatcher: I don't think we need this one + steps.Add (new ManagedRegistrarStep ()); + steps.Add (new TrimmableRegistrarStep ()); + steps.Add (new ManagedRegistrarLookupTablesStep ()); + steps.Add (new InlineClassGetHandleStep ()); + steps.Add (new SaveAssembliesStep ()); + return RunSteps (steps, out exceptions); } @@ -203,10 +219,35 @@ bool RunSteps (IList steps, out List e var linkContext = configuration.DerivedLinkContext; - foreach (var step in steps) { - step.Process (linkContext); + // We detect whether a step modified any assemblies by subscribing to the AppBundleRewriter's + // AssemblySaved callback, which is called whenever an assembly is modified. All the steps that + // modify assemblies go through the AppBundleRewriter to do so. + Action? assemblySavedHandler = null; + try { + foreach (var step in steps) { + // Subscribe once the assemblies have been loaded: accessing the AppBundleRewriter before + // that point would create it without finding the corlib and platform assemblies. + if (assemblySavedHandler is null && configuration.Assemblies.Count > 0) { + assemblySavedHandler = (_) => currentStepModifiedAssemblies = true; + configuration.AppBundleRewriter.AssemblySaved += assemblySavedHandler; + } + + currentStepModifiedAssemblies = false; + var watch = Stopwatch.StartNew (); + step.Process (linkContext); + watch.Stop (); + StepExecutions.Add (new StepExecution (step.GetType ().Name, watch.Elapsed, currentStepModifiedAssemblies)); + } + } finally { + if (assemblySavedHandler is not null) + configuration.AppBundleRewriter.AssemblySaved -= assemblySavedHandler; } + // The post-processing pass flushes its MSBuild output as its last step (DoneStep). The preparation + // pass has no DoneStep, so flush here so that its MSBuild output properties are written. + if (!configuration.Application.IsPostProcessingAssemblies) + configuration.FlushOutputForMSBuild (); + return exceptions.Count == 0; } @@ -219,6 +260,9 @@ public void Dispose () } } +// The result of executing a single step: its name, how long it took, and whether it modified any assemblies. +public record struct StepExecution (string Name, TimeSpan Duration, bool ModifiedAssemblies); + public class AssemblyPreparerInfo { internal AssemblyDefinition? Assembly { get; set; } internal bool IsCILAssembly { get; set; } diff --git a/tools/devops/automation/templates/build/build.yml b/tools/devops/automation/templates/build/build.yml index 1eff352e19b6..234079f64821 100644 --- a/tools/devops/automation/templates/build/build.yml +++ b/tools/devops/automation/templates/build/build.yml @@ -238,18 +238,17 @@ steps: # report the result of the build. - pwsh: | + # Only report failures, not successes. + if (("$Env:AGENT_JOBSTATUS" -eq "Succeeded") -or ("$Env:AGENT_JOBSTATUS" -eq "SucceededWithIssues")) { + Write-Host "Build passed, skipping comment." + return + } Import-Module $Env:SYSTEM_DEFAULTWORKINGDIRECTORY\$Env:BUILD_REPOSITORY_TITLE\tools\devops\automation\scripts\MaciosCI.psd1 $githubComments = New-GitHubCommentsObjectFromUrl -Url "$(Build.Repository.Uri)" -Token $(GitHub.Token) -Hash $Env:COMMENT_HASH $commentMessage = [System.Text.StringBuilder]::new() - if (("$Env:AGENT_JOBSTATUS" -eq "Succeeded") -or ("$Env:AGENT_JOBSTATUS" -eq "SucceededWithIssues")) { - $commentTitle = "Build passed ($(System.JobDisplayName))" - $commentIcon = ":white_check_mark:" - $commentMessage.AppendLine("") - } else { - $commentTitle = "Build failed ($(System.JobDisplayName))" - $commentIcon = ":fire:" - $commentMessage.AppendLine("Build failed for the job '$(System.JobDisplayName)' (with job status '$Env:AGENT_JOBSTATUS')") - } + $commentTitle = "Build failed ($(System.JobDisplayName))" + $commentIcon = ":fire:" + $commentMessage.AppendLine("Build failed for the job '$(System.JobDisplayName)' (with job status '$Env:AGENT_JOBSTATUS')") $githubComments.NewCommentFromMessage($commentTitle, $commentIcon, $commentMessage.ToString(), "build $(System.JobDisplayName)") condition: always() displayName: 'Report build result' diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index e9257f6727a5..db82fb797034 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -29,6 +29,9 @@ class AppBundleRewriter { AssemblyDefinition? corlib_assembly; AssemblyDefinition? platform_assembly; + // Invoked whenever an assembly is modified (i.e. whenever SaveAssembly is called). + public Action? AssemblySaved { get; set; } + public AssemblyDefinition CurrentAssembly { get { if (current_assembly is null) @@ -1455,6 +1458,7 @@ void SaveAssembly (AssemblyDefinition assembly) { if (assembly != CurrentAssembly && assembly != PlatformAssembly) throw new InvalidOperationException ($"Can't save assembly {assembly.Name} because it's not the current assembly ({CurrentAssembly.Name}) or the platform assembly ({PlatformAssembly.Name})."); + AssemblySaved?.Invoke (assembly); var annotations = configuration.Context.Annotations; var action = annotations.GetAction (assembly); if (action == AssemblyAction.Copy) { diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index a5f94cceb61d..ca103ddf6557 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -30,6 +30,9 @@ public class LinkerConfiguration { public string DedupAssembly = string.Empty; public string CacheDirectory { get; private set; } = string.Empty; public Version? DeploymentTarget { get; private set; } + // The user-provided value of the $(DynamicRegistrationSupported) MSBuild property (null if not set). + // When set, RegistrarRemovalTrackingStep doesn't need to run in the assembly-preparer. + public bool? DynamicRegistrationSupported { get; private set; } public HashSet FrameworkAssemblies { get; private set; } = new HashSet (); public string IntermediateLinkDir { get; private set; } = string.Empty; public bool InvariantGlobalization { get; private set; } @@ -43,6 +46,9 @@ public class LinkerConfiguration { public HashSet FieldSymbols { get; } = new HashSet (); public string IntermediateOutputPath { get; private set; } = string.Empty; public string ItemsDirectory { get; private set; } = string.Empty; + // The files the assembly-preparer writes its MSBuild output properties to (one per pass). + public string MSBuildOutputFile { get; private set; } = string.Empty; + public string MSBuildPostProcessOutputFile { get; private set; } = string.Empty; public bool IsSimulatorBuild { get; private set; } public string PartialStaticRegistrarLibrary { get; set; } = string.Empty; public ApplePlatform Platform { get; private set; } @@ -100,6 +106,10 @@ public List Exceptions { Dictionary> msbuild_items = new Dictionary> (); + // MSBuild output properties (property name -> value), written (alphabetically sorted) to the + // MSBuild output file by FlushOutputForMSBuild. + SortedDictionary msbuild_properties = new SortedDictionary (StringComparer.Ordinal); + AppBundleRewriter? abr; internal AppBundleRewriter AppBundleRewriter { get { @@ -286,6 +296,24 @@ Configurator GetConfigurator (string linker_file) } }) )}, + { "DynamicRegistrationSupported", ( + // This is the user-overridable $(DynamicRegistrationSupported) MSBuild property. It maps to + // the RemoveDynamicRegistrar optimization (inverted): if dynamic registration is supported, + // then we're not removing the dynamic registrar. When set, RegistrarRemovalTrackingStep doesn't + // need to run in the assembly-preparer (the value is passed straight through to the trimmer + // feature switch), and it won't recompute the value in the real linker either. + new LoadValue ((key, value) => { + if (string.IsNullOrEmpty (value)) + return; // Not set: RegistrarRemovalTrackingStep will compute a default value. + if (!TryParseOptionalBoolean (value, out var dynamicRegistrationSupported)) + throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); + if (dynamicRegistrationSupported.HasValue) { + DynamicRegistrationSupported = dynamicRegistrationSupported.Value; + Application.Optimizations.RemoveDynamicRegistrar = !dynamicRegistrationSupported.Value; + } + }), + new SaveValue ((key, storage) => saveNullableBool (key, DynamicRegistrationSupported, storage)) + )}, { "EnableSGenConc", ( new LoadValue ((key, value) => Application.EnableSGenConc = string.Equals ("true", value, StringComparison.OrdinalIgnoreCase)), new SaveValue ((key, storage) => storage.Add ($"{key}={(Application.EnableSGenConc ? "true" : "false")}")) @@ -404,6 +432,16 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => Application.MonoLibraries.Add (value)), new SaveValue ((key, storage) => storage.AddRange (Application.MonoLibraries.OrderBy (v => v).Select (v => $"{key}={v}"))) )}, + { "MSBuildOutputFile", ( + // The file the assembly-preparer's preparation pass writes its MSBuild output properties to. + new LoadValue ((key, value) => MSBuildOutputFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, MSBuildOutputFile, storage)) + )}, + { "MSBuildPostProcessOutputFile", ( + // The file the assembly-preparer's post-processing pass writes its MSBuild output properties to. + new LoadValue ((key, value) => MSBuildPostProcessOutputFile = value), + new SaveValue ((key, storage) => saveNonEmpty (key, MSBuildPostProcessOutputFile, storage)) + )}, { "MtouchFloat32", ( new LoadValue ((key, value) => loadNullableBool (key, value, out Application.AotFloat32)), new SaveValue ((key, storage) => saveNullableBool (key, Application.AotFloat32, storage)) @@ -866,25 +904,48 @@ public void WriteOutputForMSBuild (string itemName, List items) } } + // Register an MSBuild output property. The collected properties are written to the MSBuild + // output file (see FlushOutputForMSBuild) so that MSBuild can read them back. + public void SetOutputForMSBuild (string propertyName, string value) + { + msbuild_properties [propertyName] = value; + } + public void FlushOutputForMSBuild () { - Directory.CreateDirectory (ItemsDirectory); - foreach (var kvp in msbuild_items) { - var itemName = kvp.Key; - var items = kvp.Value; - - var xmlNs = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003"); - var elements = items.Select (item => - new XElement (xmlNs + itemName, - new XAttribute ("Include", item.Include), - item.Metadata.Select (metadata => new XElement (xmlNs + metadata.Key, metadata.Value)))); - - var document = new XDocument ( - new XElement (xmlNs + "Project", - new XElement (xmlNs + "ItemGroup", - elements))); - - document.Save (Path.Combine (ItemsDirectory, itemName + ".items")); + // ItemsDirectory isn't set when running in the assembly-preparer, so only write + // the item files when we have a directory to write them to. + if (!string.IsNullOrEmpty (ItemsDirectory)) { + Directory.CreateDirectory (ItemsDirectory); + foreach (var kvp in msbuild_items) { + var itemName = kvp.Key; + var items = kvp.Value; + + var xmlNs = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003"); + var elements = items.Select (item => + new XElement (xmlNs + itemName, + new XAttribute ("Include", item.Include), + item.Metadata.Select (metadata => new XElement (xmlNs + metadata.Key, metadata.Value)))); + + var document = new XDocument ( + new XElement (xmlNs + "Project", + new XElement (xmlNs + "ItemGroup", + elements))); + + document.Save (Path.Combine (ItemsDirectory, itemName + ".items")); + } + } + + // Write the collected MSBuild output properties (alphabetically sorted, one 'Name=Value' per line) + // to the output file for the current pass, so that MSBuild can read them back. We always write the + // file (even when there are no properties), so it's a consistent, persistent artifact of the pass + // (it's added to FileWrites by the _PrepareAssemblies/_PostprocessAssemblies targets). + var outputFile = Application.IsPostProcessingAssemblies ? MSBuildPostProcessOutputFile : MSBuildOutputFile; + if (!string.IsNullOrEmpty (outputFile)) { + var directory = Path.GetDirectoryName (outputFile); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + File.WriteAllLines (outputFile, msbuild_properties.Select (kvp => $"{kvp.Key}={kvp.Value}")); } } diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/RegistrarRemovalTrackingStep.cs index c295752c86cc..36f9e17306fb 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/RegistrarRemovalTrackingStep.cs @@ -28,7 +28,9 @@ protected override void TryProcessAssembly (AssemblyDefinition assembly) Process (assembly); } +#if !ASSEMBLY_PREPARER AssemblyDefinition? PlatformAssembly; +#endif bool dynamic_registration_support_required; @@ -49,7 +51,9 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) if (Profile.IsProductAssembly (assembly)) { if (Annotations.GetAction (assembly) != AssemblyAction.Link) return false; +#if !ASSEMBLY_PREPARER PlatformAssembly = assembly; +#endif } // Can't touch the forbidden fruit in the product assembly unless there's a reference to it @@ -166,20 +170,27 @@ protected override void TryEndProcess () App.Log (4, "Optimization dynamic registrar removal: {0}", Optimizations.RemoveDynamicRegistrar.Value ? "enabled" : "disabled"); - if (Optimizations.RemoveDynamicRegistrar.Value) { +#if ASSEMBLY_PREPARER + // In the assembly-preparer we don't rewrite the platform assembly. Instead we surface the computed + // value to MSBuild (as the DynamicRegistrationSupported output property), which enables the + // 'ObjCRuntime.Runtime.DynamicRegistrationSupported' trimmer feature switch so that ILLink hardcodes + // Runtime.DynamicRegistrationSupported. This way the assembly-preparer doesn't have to modify (and + // thus re-save) any assembly. + Configuration.SetOutputForMSBuild ("DynamicRegistrationSupported", App.DynamicRegistrationSupported ? "true" : "false"); +#else + if (Optimizations.RemoveDynamicRegistrar.Value && PlatformAssembly is not null) { // ILLink will optimize `Runtime.Initialize` based on `DynamicRegistrationSupported` returning a constant (`true`) // and this will runs before we have the chance to set it to `false` in `CoreOptimizedGeneratedCode` so we instead // do the change here so the linker can do this without further ado // note: it does not matter for _legacy_ so we apply the change (to earlier) to minimize the difference between them - if (PlatformAssembly is not null) { - var method = PlatformAssembly.MainModule.GetType ("ObjCRuntime.Runtime").Methods.First ((n) => n.Name == "get_DynamicRegistrationSupported"); - // Rewrite to return 'false' - var instr = method.Body.Instructions; - instr.Clear (); - instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); - instr.Add (Instruction.Create (OpCodes.Ret)); - } + var method = PlatformAssembly.MainModule.GetType ("ObjCRuntime.Runtime").Methods.First ((n) => n.Name == "get_DynamicRegistrationSupported"); + // Rewrite to return 'false' + var instr = method.Body.Instructions; + instr.Clear (); + instr.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + instr.Add (Instruction.Create (OpCodes.Ret)); } +#endif } } }