{ feature_X_cleanup },
feature_X_hello,
};
-
+
MonoComponentFeatureX *
mono_component_feature_X_init (void) { return &fn_table; }
-
+
void feature_X_cleanup (MonoComponent *self)
{
static int cleaned = 0;
{ feature_X_cleanup },
feature_X_hello,
};
-
+
MonoComponentFeatureX *
mono_component_feature_X_init (void) { return &fn_table; }
-
+
void feature_X_cleanup (MonoComponent *self)
{
static int cleaned = 0;
```c
MonoComponentFeatureX*
mono_component_feature_X (void);
-
+
...
MonoComponentFeatureX*
mono_component_feature_X_stub_init (void);
* Add an entry to the `components` list to load the component to `mono/metadata/components.c`, and also implement the getter for the component:
```c
static MonoComponentFeatureX *feature_X = NULL;
-
+
MonoComponentEntry components[] = {
...
{"feature_X", "feature_X", COMPONENT_INIT_FUNC (feature_X), (MonoComponent**)&feature_X, NULL },
## Detailed design - Packaging and runtime packs
The components are building blocks to put together a functional runtime. The
-runtime pack includes the base runtime and the components and additional
-properties and targets that enable the workload to construct a runtime for
-various scenarios.
-
-In each runtime pack we include:
-
-- The compiled compnents for the apropriate host architectures in a well-known subdirectory
-- An MSBuild props file that defines an item group that list each component name and has metadata that indicates:
- - the path to the component in the runtime pack
- - the path to the stub component in the runtime pack (if components are static)
-- An MSBuild targets file that defines targets to copy a specified set of components to the app publish folder (if components are dynamic); or to link the runtime together with stubs and a set of enabled components (if components are static)
-
-** TODO ** Write this up in more detail
+runtime pack includes the base runtime and the components. The mono workload
+includes the runtime pack and additional tasks, properties and targets that
+enable the workload to construct a runtime for various scenarios.
+
+For the target RID, we expose:
+
+- `@(_MonoRuntimeComponentLinking)` set to either `'static'` or `'dynamic'` depending on whether the
+ current runtime pack for the current target includes runtime components as static archives or as
+ shared libraries, respectively.
+- `@(_MonoRuntimeComponentSharedLibExt)` and `@(_MonoRuntimeComponentStaticLibExt)` set to the file
+ extension of the runtime components for the current target (ie, `'.a', '.so', '.dylib'` etc).
+- `@(_MonoRuntimeAvailableComponents)` a list of component names without the `lib` prefix (if any)
+ or file extensions. For example: `'hot_reload; diagnostics_tracing'`.
+
+Each of the above item lists has `RuntimeIdentifier` metadata. For technical reasons the mono
+workload will provide a single `@(_MonoRuntimeAvailableComponent)` item list for all platforms. We
+use the `RuntimeIdentifier` metadata to filter out the details applicable for the current platform.
+
+- The target `_MonoSelectRuntimeComponents` that has the following inputs and outputs:
+ - input `@(_MonoComponent)` (to be set by the workload) : a list of components that a workload wants to use for the current
+ app. It is an error if this specifies any unknown component name.
+ - output `@(_MonoRuntimeSelectedComponents)` and `@(_MonoRuntimeSelectedStubComponents)` The names
+ of the components that were (resp, were not) selected. For example `'hot_reload;
+ diagnostics_tracing'`. Each item has two metadata properties `ComponentLib` and
+ `ComponentStubLib` (which may be empty) that specify the name of the static or dynamic library
+ of the component. This is not the main output of the target, it's primarily for debugging.
+ - output `@(_MonoRuntimeComponentLink)` a list of library names (relative to the `native/`
+ subdirectory of the runtime pack) that (for dynamic components) must be placed next to the
+ runtime in the application bundle, or (for static components) that must be linked with the
+ runtime to enable the components' functionality. Each item in the list has metadata
+ `ComponentName` (e.g. `'hot_reload'`), `IsStub` (`true` or `false`), `Linking` (`'static'` or
+ `'dynamic'`). This output should be used by the workloads when linking the app and runtime if
+ the workload uses an allow list of native libraries to link or bundle.
+ - output `@(_MonoRuntimeComponentDontLink)` a list of library names (relative to the `native/`
+ subdirectory of the runtime pack) that should be excluded from the application bundle (for
+ dynamic linking) or that should not be passed to the native linker (for static linking). This
+ output should be used by workloads that just link or bundle every native library from `native/`
+ in order to filter the contents of the subdirectory to exclude the disabled components (and to
+ exclude the static library stubs for the enabled components when static linking).
+
+Generally workloads should only use one of `@(_MonoRuntimeComponentLink)` or
+`@(_MonoRuntimeComponentDontLink)`, depending on whether they use an allow or block list for the
+contents of the `native/` subdirectory.
+
+Example fragment (assuming the mono workload has been imported):
+
+```xml
+ <Project>
+ <ItemGroup Condition="'$(Configuration)' == 'Debug'">
+ <_MonoComponent Include="hot_reload;diagnostics_tracing" />
+ </ItemGroup>
+
+ <Target Name="PrintComponents" DependsOnTargets="_MonoSelectRuntimeComponents">
+ <Message Importance="High" Text="Runtime identifier: $(RuntimeIdentifier)" />
+ <Message Importance="High" Text="Selected : @(_MonoRuntimeSelectedComponents) %(ComponentLib)" />
+ <Message Importance="High" Text="Stubbed out : @(_MonoRuntimeSelectedStubComponents) %(ComponentStubLib)" />
+ <Message Importance="High" Text="Linking with lib @(_MonoRuntimeComponentLink) Stub: %(IsStub) Linking: %(Linking) Component: %(ComponentName)"/>
+
+ <Message Importance="High" Text="UnSelected : @(_MonoRuntimeUnSelectedComponents) %(ComponentLib)" />
+ <Message Importance="High" Text="Exclude these from linking: @(_MonoRuntimeComponentDontLink) Stub: %(IsStub) Linking: %(Linking) Component: %(ComponentName)" />
+ </Target>
+ </Project>
+```
<TargetPath>runtimes/$(RuntimeIdentifier)/native/include/%(RecursiveDir)</TargetPath>
</RuntimeFiles>
+ <RuntimeFiles Condition="'$(RuntimeFlavor)' == 'mono'"
+ Include="$(MonoArtifactsPath)\build\**\*.*"
+ ExcludeFromDataFiles="true">
+ <TargetPath>runtimes/$(RuntimeIdentifier)/build/%(RecursiveDir)</TargetPath>
+ </RuntimeFiles>
+
<CoreCLRCrossTargetFiles PackOnly="true" />
<CoreCLRCrossTargetFiles Condition="'%(FileName)' == 'clrjit' or '%(FileName)' == 'libclrjit'">
<TargetPath>runtimes/$(CoreCLRCrossTargetComponentDirName)_$(TargetArchitecture)/native</TargetPath>
<ItemGroup Condition="'$(MonoComponentsStatic)' == 'true'">
<_MonoCMakeArgs Include="-DSTATIC_COMPONENTS=1" />
</ItemGroup>
+ <ItemGroup>
+ <_MonoCMakeArgs Include="-DMONO_COMPONENTS_RID=$(TargetOS)-$(TargetArchitecture)" />
+ </ItemGroup>
<PropertyGroup>
<_MonoCFLAGSOption>-DCMAKE_C_FLAGS="@(_MonoCPPFLAGS, ' ') @(_MonoCFLAGS, ' ')"</_MonoCFLAGSOption>
<Destination>$(RuntimeBinDir)cross\$(PackageRID)\opt$(ExeExt)</Destination>
</_MonoRuntimeArtifacts>
<_MonoIncludeArtifacts Include="$(MonoObjDir)out\include\**" />
+ <_MonoRuntimeBuildArtifacts Include="$(MonoObjDir)\build\**" />
<_MonoRuntimeArtifacts Condition="'$(_MonoIncludeInterpStaticFiles)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-ee-interp.a">
<Destination>$(RuntimeBinDir)libmono-ee-interp.a</Destination>
</_MonoRuntimeArtifacts>
SkipUnchangedFiles="true"
Condition="'$(MonoGenerateOffsetsOSGroups)' == '' and ('$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true' or '$(TargetsAndroid)' == 'true' or '$(TargetsBrowser)' == 'true')"/>
+ <Copy SourceFiles="@(_MonoRuntimeBuildArtifacts)"
+ DestinationFiles="@(_MonoRuntimeBuildArtifacts->'$(RuntimeBinDir)build\%(RecursiveDir)%(Filename)%(Extension)')"
+ SkipUnchangedFiles="true"
+ Condition="'$(BuildMonoAOTCrossCompilerOnly)' != 'true'" />
+
<Exec Condition="'$(BuildMonoAOTCrossCompilerOnly)' != 'true' and '$(MonoGenerateOffsetsOSGroups)' == '' and ('$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true')" Command="install_name_tool -id @rpath/$(MonoFileName) $(RuntimeBinDir)$(MonoFileName)" />
</Target>
#define a library for each component and component stub
function(define_component_libs)
+ # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets
if (NOT DISABLE_LIBS)
foreach(component IN LISTS components)
add_library("mono-component-${component}-static" STATIC $<TARGET_OBJECTS:${component}-objects>)
if(NOT DISABLE_COMPONENTS AND NOT STATIC_COMPONENTS)
# define a shared library for each component
foreach(component IN LISTS components)
+ # NOTE: keep library naming pattern in sync with RuntimeComponentManifest.targets
if(HOST_WIN32)
add_library("mono-component-${component}" SHARED "${${component}-sources}")
target_compile_definitions("mono-component-${component}" PRIVATE -DCOMPILING_COMPONENT_DYNAMIC;-DMONO_DLL_IMPORT)
list(APPEND mono-components-stub-objects $<TARGET_OBJECTS:${component}-stub-objects>)
endforeach()
+if(NOT MONO_CROSS_COMPILE)
+ set(TemplateMonoRuntimeComponentSharedLibExt "${CMAKE_SHARED_LIBRARY_SUFFIX}")
+ set(TemplateMonoRuntimeComponentStaticLibExt "${CMAKE_STATIC_LIBRARY_SUFFIX}")
+ set(TemplateRuntimeIdentifier "${MONO_COMPONENTS_RID}")
+ if(DISABLE_COMPONENTS)
+ set(TemplateMonoRuntimeComponentLinking "static")
+ set(TemplateMonoRuntimeAvailableComponents "")
+ else()
+ list(TRANSFORM components REPLACE "^(.+)$" "{ \"identity\": \"\\1\", \"RuntimeIdentifier\": \"${TemplateRuntimeIdentifier}\" }," OUTPUT_VARIABLE TemplateMonoRuntimeAvailableComponentsList)
+ list(JOIN TemplateMonoRuntimeAvailableComponentsList "\n" TemplateMonoRuntimeAvailableComponents)
+ if(STATIC_COMPONENTS)
+ set(TemplateMonoRuntimeComponentLinking "static")
+ else()
+ set(TemplateMonoRuntimeComponentLinking "dynamic")
+ endif()
+ endif()
+ # Write a RuntimeComponentManifest.json file in the artifacts/obj/mono/<host>/build/ directory
+ # without the ../.. the file would go in artifacts/obj/mono/<host>/mono/mini
+ configure_file( "${MONO_COMPONENT_PATH}/RuntimeComponentManifest.json.in" "../../build/RuntimeComponentManifest.json")
+endif()
# component tests
set(MONO_EVENTPIPE_TEST_SOURCE_PATH "${MONO_EVENTPIPE_SHIM_SOURCE_PATH}/test")
--- /dev/null
+{
+ "items": {
+ "_MonoRuntimeComponentLinking": [
+ { "identity": "${TemplateMonoRuntimeComponentLinking}", "RuntimeIdentifier": "${TemplateRuntimeIdentifier}" },
+ ],
+ "_MonoRuntimeComponentSharedLibExt": [
+ { "identity": "${TemplateMonoRuntimeComponentSharedLibExt}", "RuntimeIdentifier": "${TemplateRuntimeIdentifier}" },
+ ],
+ "_MonoRuntimeComponentStaticLibExt": [
+ { "identity": "${TemplateMonoRuntimeComponentStaticLibExt}", "RuntimeIdentifier": "${TemplateRuntimeIdentifier}" },
+ ],
+ "_MonoRuntimeAvailableComponents": [
+ ${TemplateMonoRuntimeAvailableComponents}
+ ],
+ }
+}
--- /dev/null
+<Project DefaultTargets="Build">
+ <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" />
+
+ <PropertyGroup>
+ <PackageDescription>Provides the tasks+targets, for consumption by mono-based workloads</PackageDescription>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="$(RepoTasksDir)RuntimeConfigParser\RuntimeConfigParser.csproj" />
+ <ProjectReference Include="$(RepoTasksDir)JsonToItemsTaskFactory\JsonToItemsTaskFactory.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageFile Include="Sdk\Sdk.props" TargetPath="Sdk" />
+ <PackageFile Include="Sdk\Sdk.targets" TargetPath="Sdk" />
+ <PackageFile Include="build\$(MSBuildProjectName).props" TargetPath="build" />
+ <PackageFile Include="Sdk\RuntimeConfigParserTask.props" TargetPath="Sdk" />
+ <PackageFile Include="Sdk\RuntimeComponentManifest.props" TargetPath="Sdk" />
+ <PackageFile Include="Sdk\RuntimeComponentManifest.targets" TargetPath="Sdk" />
+ </ItemGroup>
+
+ <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
+</Project>
--- /dev/null
+# Mono Runtime Host support targets
+
+This Sdk provides additional tasks and targets for workloads hosting the MonoVM .NET runtime.
+
+## component-manifest.targets
+
+See https://github.com/dotnet/runtime/blob/main/docs/design/mono/components.md
+
+## RuntimeConfigParserTask
+The `RuntimeConfigParserTask` task converts a json `runtimeconfig.json` to a binary blob for MonoVM's `monovm_runtimeconfig_initialize` API.
+To use the task in a project, reference the NuGet package, with the appropriate nuget source.
+
+### NuGet.config
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <packageSources>
+ <add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
+ </packageSources>
+</configuration>
+```
+
+### In the project file
+```xml
+<!-- Import the NuGet package into the project -->
+<ItemGroup>
+ <PackageReference Include="Microsoft.NET.Runtime.MonoTargets.Sdk" Version="<desired-dotnet-6-sdk-version>" />
+</ItemGroup>
+
+<!-- Use the RuntimeConfigParser task in a target -->
+<Target>
+ <RuntimeConfigParserTask
+ RuntimeConfigFile="$(Path_to_runtimeconfig.json_file)"
+ OutputFile="$(Path_to_generated_binary_file)"
+ RuntimeConfigReservedProperties="@(runtime_properties_reserved_by_host)">
+ </RuntimeConfigParserTask>
+</Target>
+```
--- /dev/null
+<Project>
+ <PropertyGroup>
+ <JsonToItemsTaskFactoryTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tasks\net6.0\JsonToItemsTaskFactory.dll</JsonToItemsTaskFactoryTasksAssemblyPath>
+ <JsonToItemsTaskFactoryTasksAssemblyPath Condition="'$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tasks\net472\JsonToItemsTaskFactory.dll</JsonToItemsTaskFactoryTasksAssemblyPath>
+ </PropertyGroup>
+ <UsingTask TaskName="MonoRuntimeComponentManifestReadTask" TaskFactory="JsonToItemsTaskFactory.JsonToItemsTaskFactory" AssemblyFile="$(JsonToItemsTaskFactoryTasksAssemblyPath)">
+ <ParameterGroup>
+ <_MonoRuntimeComponentSharedLibExt ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ <_MonoRuntimeComponentStaticLibExt ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ <_MonoRuntimeComponentLinking ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ <_MonoRuntimeAvailableComponents ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ </ParameterGroup>
+ </UsingTask>
+</Project>
--- /dev/null
+<Project>
+ <Target Name="_MonoReadAvailableComponentsManifest">
+ <Error Condition="'%(ResolvedRuntimePack.PackageDirectory)' == '' and '$(_MonoRuntimeComponentManifestJsonFilePath)' == ''"
+ Text="Empty ResolvedRuntimePack.PackageDirectory while trying to read runtime components manifest" />
+ <PropertyGroup>
+ <_MonoRuntimeComponentManifestJsonFilePath Condition="'$(_MonoRuntimeComponentManifestJsonFilePath)' == ''">%(ResolvedRuntimePack.PackageDirectory)\runtimes\$(RuntimeIdentifier)\build\RuntimeComponentManifest.json</_MonoRuntimeComponentManifestJsonFilePath>
+ </PropertyGroup>
+ <MonoRuntimeComponentManifestReadTask JsonFilePath="$(_MonoRuntimeComponentManifestJsonFilePath)">
+ <Output TaskParameter="_MonoRuntimeComponentSharedLibExt" ItemName="_MonoRuntimeComponentSharedLibExt" />
+ <Output TaskParameter="_MonoRuntimeComponentStaticLibExt" ItemName="_MonoRuntimeComponentStaticLibExt" />
+ <Output TaskParameter="_MonoRuntimeComponentLinking" ItemName="_MonoRuntimeComponentLinking" />
+ <Output TaskParameter="_MonoRuntimeAvailableComponents" ItemName="_MonoRuntimeAvailableComponents" />
+ </MonoRuntimeComponentManifestReadTask>
+ </Target>
+
+ <Target Name="_MonoComputeAvailableComponentDefinitions" DependsOnTargets="_MonoReadAvailableComponentsManifest">
+ <!-- Input: _MonoRuntimeAvailableComponents item list: the components provided by the runtime -->
+ <!-- Input: _MonoRuntimeComponentLinking property: either 'dynamic' or 'static'. Are the components static or shared libraries. -->
+ <!-- Output: _MonoRuntimeComponentName item list: the components provided by the runtime, with ComponentLib and ComponentStubLib metadata that gives the filenames of the components relative to the runtime pack native subdirectory -->
+ <PropertyGroup>
+ <_MonoRuntimeComponentCurrentSharedLibExt>@(_MonoRuntimeComponentSharedLibExt->WithMetadataValue('RuntimeIdentifier', '$(RuntimeIdentifier)'))</_MonoRuntimeComponentCurrentSharedLibExt>
+ <_MonoRuntimeComponentCurrentStaticLibExt>@(_MonoRuntimeComponentStaticLibExt->WithMetadataValue('RuntimeIdentifier', '$(RuntimeIdentifier)'))</_MonoRuntimeComponentCurrentStaticLibExt>
+ <_MonoRuntimeComponentCurrentLinking>@(_MonoRuntimeComponentLinking->WithMetadataValue('RuntimeIdentifier', '$(RuntimeIdentifier)'))</_MonoRuntimeComponentCurrentLinking>
+ </PropertyGroup>
+ <Error Condition="'$(_MonoRuntimeComponentCurrentSharedLibExt)' == '' or '$(_MonoRuntimeComponentCurrentStaticLibExt)' == ''"
+ Text="No _MonoRuntimeComponentSharedLibExt or _MonoRuntimeComponentStaticLibExt for '$(RuntimeIdentifier)'. Malformed runtime pack?" />
+ <Error Condition="'$(_MonoRuntimeComponentCurrentLinking)' != 'dynamic' and '$(_MonoRuntimeComponentCurrentLinking)' != 'static'"
+ Text="Value of _MonoRuntimeComponentLinking for '$(RuntimeIdentifier)' must be 'dynamic' or 'static' it is '$(_MonoRuntimeComponentCurrentLinking)'. Malformed runtime pack?" />
+ <ItemGroup Condition="'$(_MonoRuntimeComponentCurrentLinking)' == 'dynamic'">
+ <_MonoRuntimeComponentName Include="@(_MonoRuntimeAvailableComponents)" Condition="'%(RuntimeIdentifier)' == '$(RuntimeIdentifier)'">
+ <ComponentLib>libmono-component-%(Identity)$(_MonoRuntimeComponentCurrentSharedLibExt)</ComponentLib>
+ </_MonoRuntimeComponentName>
+ </ItemGroup>
+ <ItemGroup Condition="'$(_MonoRuntimeComponentCurrentLinking)' == 'static'">
+ <_MonoRuntimeComponentName Include="@(_MonoRuntimeAvailableComponents)" Condition="'%(RuntimeIdentifier)' == '$(RuntimeIdentifier)'">
+ <ComponentLib>libmono-component-%(Identity)-static$(_MonoRuntimeComponentCurrentStaticLibExt)</ComponentLib>
+ <ComponentStubLib>libmono-component-%(Identity)-stub-static$(_MonoRuntimeComponentCurrentStaticLibExt)</ComponentStubLib>
+ </_MonoRuntimeComponentName>
+ </ItemGroup>
+ </Target>
+
+ <Target Name="_MonoSelectRuntimeComponents" DependsOnTargets="_MonoComputeAvailableComponentDefinitions">
+ <!-- Input: _MonoComponent item list of names of components that will be enabled (to be defined
+ by the workload) -->
+ <!-- Output: _MonoRuntimeSelectedComponents subset of _MonoRuntimeComponentName items that were
+ in _MonoComponent -->
+ <!-- Output: _MonoRuntimeSelectedStubComponents (static linking only) subset of
+ _MonoRuntimeComponentName items that were not in _MonoComponent -->
+ <!-- Output: _MonoRuntimeUnSelectedComponents subset of _MonoRuntimeComponentName items that were not in _MonoComponent. That is, the components that are available but that weren't selected. -->
+ <!-- Output: _MonoRuntimeUnSelectedStubComponents (static linking only) subset of _MonoRuntimeComponentName items that were in _MonoComponent. That is, components that were selected - so their stubs should not be linked. -->
+ <!-- Output: _MonoRuntimeComponentLink item list of the filenames to link (relative paths
+ relative to the native/ directory of the runtime pack). For dynamic linking, this is only
+ the enabled components. For static linking, it also includes stubs for the non-selected
+ components -->
+ <!-- Output: _MonoRuntimeComponentDontLink item list of the filename to exclude from linking
+ (relative paths relative to the native/ directory of the runtime pack). For static linking,
+ this is stubs or components that must not be passed to the native linker. For dynamic
+ linking, this is copmonent shared libraries that must not be bundled with the app
+ -->
+ <!-- Generally the workload should only use one of _MonoRuntimeComponentLink or
+ _MonoRuntimeComponentDontLink. For example, a workload that doesn't understand the
+ contents of native/ in details will generally bundle every native library from native/.
+ Such a workload should then use DontLink to exclude the disabled components. On the other
+ hand a workload that explicitly picks the native libraries from native/ should use
+ _MonoRuntimeComponentLink to pick just the needed runtime component artifacts. -->
+
+ <!-- The target also depends on the following items that are provided by the component-manifest.props from the runtime pack -->
+ <!-- _MonoRuntimeComponentLinking item list: either 'dynamic' or 'static' (defined by the runtime for each possible RuntimeIdentifier) -->
+ <!-- _MonoRuntimeComponentName item list: the components for the current RuntimeIdentifier (defined by _MonoComputeAvailableComponentDefinitions above ) -->
+
+ <ItemGroup Condition="'$(_MonoRuntimeComponentCurrentLinking)' == 'dynamic' or '$(_MonoRuntimeComponentCurrentLinking)' == 'static'">
+ <!-- take the intersection of the two item lists. -->
+ <!-- n.b. copies the metadata from _MonoRuntimeComponentName to _MonoRuntimeSelectedComponents -->
+ <_MonoRuntimeComponentNameForRid Include="@(_MonoRuntimeComponentName)" Condition="'%(RuntimeIdentifier)' == '$(RuntimeIdentifier)'" />
+ <_MonoRuntimeSelectedComponents Include="@(_MonoRuntimeComponentNameForRid)" Condition="'@(_MonoRuntimeComponentNameForRid)' == '@(_MonoComponent)' and '%(Identity)' != ''" />
+ <_MonoRuntimeUnSelectedComponents Include="@(_MonoRuntimeComponentNameForRid)" Exclude="@(_MonoComponent)" />
+ <_MonoRuntimeComponentLink Include="%(_MonoRuntimeSelectedComponents.ComponentLib)">
+ <Linking>$(_MonoRuntimeComponentCurrentLinking)</Linking>
+ <IsStub>false</IsStub>
+ <ComponentName>%(_MonoRuntimeSelectedComponents.Identity)</ComponentName>
+ </_MonoRuntimeComponentLink>
+ <_MonoRuntimeComponentDontLink Include="%(_MonoRuntimeUnSelectedComponents.ComponentLib)">
+ <Linking>$(_MonoRuntimeComponentCurrentLinking)</Linking>
+ <IsStub>false</IsStub>
+ <ComponentName>%(_MonoRuntimeUnSelectedComponents.Identity)</ComponentName>
+ </_MonoRuntimeComponentDontLink>
+ <!-- non-empty if any unknown components are specified -->
+ <_MonoRuntimeSelectedMissingComponents Include="@(_MonoComponent)" Exclude="@(_MonoRuntimeComponentNameForRid)" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(_MonoRuntimeComponentCurrentLinking)' == 'static'">
+ <!-- Take difference _MonoRuntimeComponentName - _MonoComponent. The remaining components are stubbed out -->
+ <_MonoRuntimeSelectedStubComponents Include="@(_MonoRuntimeComponentNameForRid)" Exclude="@(_MonoComponent)" />
+ <_MonoRuntimeUnSelectedStubComponents Include="@(_MonoRuntimeComponentNameForRid)" Condition="'@(_MonoRuntimeComponentNameForRid)' == '@(_MonoComponent)' and '%(Identity)' != ''" />
+ <_MonoRuntimeComponentLink Include="%(_MonoRuntimeSelectedStubComponents.ComponentStubLib)">
+ <Linking>$(_MonoRuntimeComponentCurrentLinking)</Linking>
+ <IsStub>true</IsStub>
+ <ComponentName>%(_MonoRuntimeSelectedStubComponents.Identity)</ComponentName>
+ </_MonoRuntimeComponentLink>
+ <_MonoRuntimeComponentDontLink Include="%(_MonoRuntimeUnSelectedStubComponents.ComponentStubLib)">
+ <Linking>$(_MonoRuntimeComponentCurrentLinking)</Linking>
+ <IsStub>true</IsStub>
+ <ComponentName>%(_MonoRuntimeUnSelectedStubComponents.Identity)</ComponentName>
+ </_MonoRuntimeComponentDontLink>
+ </ItemGroup>
+
+ <Error Condition="'$(_MonoRuntimeComponentCurrentLinking)' != 'dynamic' and '$(_MonoRuntimeComponentCurrentLinking)' != 'static'" Text="_MonoRuntimeComponentCurrentLinking is not dynamic or static. It is '$(_MonoRuntimeComponentCurrentLinking)'. RuntimeIdentifier is '$(RuntimeIdentifier)'." />
+ <Error Condition="@(_MonoRuntimeSelectedMissingComponents->Count()) != 0"
+ Text="_MonoComponent item list includes components '@(_MonoRuntimeSelectedMissingComponents)' which are not defined in the runtime pack. Runtime pack includes '@(_MonoRuntimeComponentNameForRid)'" />
+
+ </Target>
+</Project>
--- /dev/null
+<Project>
+ <Import Project="$(MSBuildThisFileDirectory)\RuntimeConfigParserTask.props" />
+ <Import Project="$(MSBuildThisFileDirectory)\RuntimeComponentManifest.props" />
+</Project>
--- /dev/null
+<Project>
+ <Import Project="$(MSBuildThisFileDirectory)\RuntimeComponentManifest.targets" />
+</Project>
+++ /dev/null
-<Project DefaultTargets="Build">
- <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" />
-
- <PropertyGroup>
- <PackageDescription>Provides the RuntimeConfigParser task</PackageDescription>
- </PropertyGroup>
-
- <ItemGroup>
- <ProjectReference Include="$(RepoTasksDir)RuntimeConfigParser\RuntimeConfigParser.csproj" />
-
- <PackageFile Include="Sdk\Sdk.props" TargetPath="Sdk" />
- <PackageFile Include="build\$(MSBuildProjectName).props" TargetPath="build" />
- </ItemGroup>
-
- <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
-</Project>
+++ /dev/null
-# RuntimeConfigParser MSBuild Task NuPkg
-The `RuntimeConfigParser` MSBuild task is also useful outside the context of `dotnet/runtime`. The task is made available through a NuGet Package containing the `RuntimeConfigParser.dll` assembly produced from building `RuntimeConfigParser.csproj`. To use the task in a project, reference the NuGet package, with the appropriate nuget source.
-
-## NuGet.config
-```
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
- <packageSources>
- <add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
- </packageSources>
-</configuration>
-```
-
-## In the project file
-```
-<!-- Import the NuGet package into the project -->
-<ItemGroup>
- <PackageReference Include="Microsoft.NET.Runtime.RuntimeConfigParser.Task" Version="<desired-dotnet-6-sdk-version>" />
-</ItemGroup>
-
-<!-- Use the RuntimeConfigParser task in a target -->
-<Target>
- <RuntimeConfigParserTask
- RuntimeConfigFile="$(Path_to_runtimeconfig.json_file)"
- OutputFile="$(Path_to_generated_binary_file)"
- RuntimeConfigReservedProperties="@(runtime_properties_reserved_by_host)">
- </RuntimeConfigParserTask>
-</Target>
-```
"abstract": true,
"description": "Shared native build tooling for Mono runtime",
"packs": [
- "Microsoft.NET.Runtime.RuntimeConfigParser.Task",
"Microsoft.NET.Runtime.MonoAOTCompiler.Task",
+ "Microsoft.NET.Runtime.MonoTargets.Sdk",
],
}
},
"packs": {
- "Microsoft.NET.Runtime.RuntimeConfigParser.Task": {
+ "Microsoft.NET.Runtime.MonoAOTCompiler.Task": {
"kind": "Sdk",
"version": "${PackageVersion}"
},
- "Microsoft.NET.Runtime.MonoAOTCompiler.Task": {
+ "Microsoft.NET.Runtime.MonoTargets.Sdk": {
"kind": "Sdk",
"version": "${PackageVersion}"
},
"version": "${PackageVersion}"
},
}
-}
\ No newline at end of file
+}
<Import Condition="'$(RunAOTCompilation)' == 'true'" Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoAOTCompiler.Task" />
- <Import Condition="'$(TargetPlatformIdentifier)' == 'android'" Project="Sdk.props" Sdk="Microsoft.NET.Runtime.RuntimeConfigParser.Task" />
+ <ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'android'">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
+ </ImportGroup>
<ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'android' and '$(RunAOTCompilation)' == 'true'">
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.android-x86" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.android-x64" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64" />
</ImportGroup>
- <ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'ios'">
- <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.RuntimeConfigParser.Task" />
- </ImportGroup>
<ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'ios' and $([MSBuild]::IsOSPlatform('osx'))">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.ios-arm64" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.iossimulator" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
</ImportGroup>
- <ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'maccatalyst'">
- <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.RuntimeConfigParser.Task" />
- </ImportGroup>
<ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'maccatalyst' and $([MSBuild]::IsOSPlatform('osx'))">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.maccatalyst" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
</ImportGroup>
- <ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'tvos'">
- <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.RuntimeConfigParser.Task" />
- </ImportGroup>
<ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'tvos' and $([MSBuild]::IsOSPlatform('osx'))">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.tvos-arm64" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
+ </ImportGroup>
+
+ <ImportGroup Condition="'$(TargetPlatformIdentifier)' == 'tvossimulator'">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.tvossimulator" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
</ImportGroup>
<ImportGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm' and '$(UsingBrowserRuntimeWorkload)' == 'true'">
+ <Import Project="Sdk.props" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.WebAssembly.Sdk" />
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.browser-wasm" />
+ <Import Project="Sdk.targets" Sdk="Microsoft.NET.Runtime.MonoTargets.Sdk" />
</ImportGroup>
+
</Project>
<ItemGroup Condition="'$(TargetsMobile)' == 'true'">
<ProjectReference Include="Microsoft.NET.Workload.Mono.Toolchain.Manifest\Microsoft.NET.Workload.Mono.Toolchain.Manifest.pkgproj" />
<ProjectReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task\Microsoft.NET.Runtime.MonoAOTCompiler.Task.pkgproj" />
- <ProjectReference Include="Microsoft.NET.Runtime.RuntimeConfigParser.Task\Microsoft.NET.Runtime.RuntimeConfigParser.Task.pkgproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="Microsoft.NET.Runtime.MonoTargets.Sdk\Microsoft.NET.Runtime.MonoTargets.Sdk.pkgproj" />
</ItemGroup>
</Project>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+#if NET472
+namespace System.Diagnostics.CodeAnalysis {
+ [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+
+ public class NotNullWhenAttribute : Attribute {
+ public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+ public bool ReturnValue { get; }
+ }
+}
+#endif
+
+namespace JsonToItemsTaskFactory
+{
+
+ /// <summary>Reads a json input blob and populates some output items</summary>
+ ///
+ /// <example>JSON should follow this structure - the toplevel "properties" and "items" keys are exact, other keys are arbitrary.
+ /// <code>
+ /// {
+ /// "properties" : {
+ /// "propName1": "value1",
+ /// "propName2": "value"
+ /// },
+ /// "items" : {
+ /// "itemName1": [ "stringValue", { "identity": "anotherValue", "metadataKey": "metadataValue", ... }, "thirdValue" ],
+ /// "itemName2": [ ... ]
+ /// }
+ /// </code>
+ ///
+ /// A task can be declared by
+ ///
+ /// <code>
+ /// <UsingTask AssemblyFile="..." TaskName="MyJsonReader" TaskFactory="Microsoft.DotNet.Runtime.Tasks.JsonToItemsTaskFactory">
+ /// <ParameterGroup>
+ /// <PropName1 ParameterType="System.String" Required="False" Output="True" />
+ /// <ItemName1 ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="False" Output="True" />
+ /// <ParameterGroup>
+ /// </UsingTask>
+ /// </code>
+ ///
+ /// And then used in a target. The `JsonFilePath' attribute is used to specify the json file to read.
+ ///
+ /// <code>
+ /// <Target Name="UseMyReader">
+ /// <MyJsonReader JsonFilePath="foo.json">
+ /// <Output TaskParameter="PropName1" PropertyName="MyParsedProperty" />
+ /// <Output TaskParameter="ItemName1" ItemName="MyParsedItems" />
+ /// </MyJsonReader>
+ /// <Message Priority="High" Text=" Got property $(MyParsedProperty) and items @(MyParsedItems)" />
+ /// </Target>
+ /// </code>
+ /// </example>
+ public class JsonToItemsTaskFactory : ITaskFactory
+ {
+ private const string JsonFilePath = "JsonFilePath";
+ private TaskPropertyInfo[]? _taskProperties;
+ private string? _taskName;
+
+ private bool _logDebugTask;
+
+ public JsonToItemsTaskFactory() {}
+
+ public string FactoryName => "JsonToItemsTaskFactory";
+
+ public Type TaskType => typeof(JsonToItemsTask);
+
+ public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> parameterGroup, string? taskBody, IBuildEngine taskFactoryLoggingHost)
+ {
+ _taskName = taskName;
+ if (taskBody != null && taskBody.StartsWith("debug", StringComparison.InvariantCultureIgnoreCase))
+ _logDebugTask = true;
+ var log = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName);
+ if (!ValidateParameterGroup (parameterGroup, log))
+ return false;
+ _taskProperties = new TaskPropertyInfo[parameterGroup.Count + 1];
+ _taskProperties[0] = new TaskPropertyInfo(nameof(JsonFilePath), typeof(string), output: false, required: true);
+ parameterGroup.Values.CopyTo(_taskProperties, 1);
+ return true;
+ }
+
+ public TaskPropertyInfo[] GetTaskParameters() => _taskProperties!;
+
+ public ITask CreateTask(IBuildEngine taskFactoryLoggingHost)
+ {
+ var log = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName);
+ if (_logDebugTask) log.LogMessage(MessageImportance.Low, "CreateTask called");
+ return new JsonToItemsTask(_taskName!, _logDebugTask);
+ }
+
+ public void CleanupTask(ITask task) {}
+
+ internal bool ValidateParameterGroup(IDictionary<string, TaskPropertyInfo> parameterGroup, TaskLoggingHelper log)
+ {
+ var taskName = _taskName ?? "";
+ foreach (var kvp in parameterGroup)
+ {
+ var propName = kvp.Key;
+ var propInfo = kvp.Value;
+ if (string.Equals(propName, nameof(JsonFilePath), StringComparison.InvariantCultureIgnoreCase))
+ {
+ log.LogError($"Task {taskName}: {nameof(JsonFilePath)} parameter must not be declared. It is implicitly added by the task.");
+ continue;
+ }
+
+ if (!propInfo.Output)
+ {
+ log.LogError($"Task {taskName}: parameter {propName} is not an output. All parameters except {nameof(JsonFilePath)} must be outputs");
+ continue;
+ }
+ if (propInfo.Required)
+ {
+ log.LogError($"Task {taskName}: parameter {propName} is an output but is marked required. That's not supported.");
+ }
+ if (typeof(ITaskItem[]).IsAssignableFrom(propInfo.PropertyType))
+ continue; // ok, an item list
+ if (typeof(string).IsAssignableFrom(propInfo.PropertyType))
+ continue; // ok, a string property
+
+ log.LogError($"Task {taskName}: parameter {propName} is not an output of type System.String or Microsoft.Build.Framework.ITaskItem[]");
+ }
+ return !log.HasLoggedErrors;
+ }
+
+ public class JsonToItemsTask : IGeneratedTask
+ {
+ private IBuildEngine? _buildEngine;
+ public IBuildEngine BuildEngine { get => _buildEngine!; set { _buildEngine = value; SetBuildEngine(value);} }
+ public ITaskHost? HostObject { get; set; }
+
+ private TaskLoggingHelper? _log;
+ private TaskLoggingHelper Log { get => _log!; set { _log = value; } }
+
+ private void SetBuildEngine(IBuildEngine buildEngine)
+ {
+ Log = new TaskLoggingHelper(buildEngine, TaskName);
+ }
+
+ public static JsonSerializerOptions JsonOptions => new()
+ {
+ PropertyNameCaseInsensitive = true,
+ AllowTrailingCommas = true,
+ };
+ private string? jsonFilePath;
+
+ private readonly bool _logDebugTask; // print stuff to the log for debugging the task
+
+ private JsonModelRoot? jsonModel;
+ public string TaskName {get;}
+ public JsonToItemsTask(string taskName, bool logDebugTask = false)
+ {
+ TaskName = taskName;
+ _logDebugTask = logDebugTask;
+ }
+
+ public bool Execute()
+ {
+ if (jsonFilePath == null)
+ {
+ Log.LogError($"no {nameof(JsonFilePath)} specified");
+ return false;
+ }
+ if (!TryGetJson(jsonFilePath, out var json))
+ return false;
+
+ if (_logDebugTask)
+ {
+ LogParsedJson(json);
+ }
+ jsonModel = json;
+ return true;
+ }
+
+ public bool TryGetJson(string jsonFilePath, [NotNullWhen(true)] out JsonModelRoot? json)
+ {
+ FileStream? file = null;
+ try
+ {
+ try
+ {
+ file = File.OpenRead(jsonFilePath);
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ Log.LogErrorFromException(fnfe);
+ json = null;
+ return false;
+ }
+ json = GetJsonAsync(jsonFilePath, file).Result;
+ if (json == null)
+ {
+ // the async task may have already caught an exception and logged it.
+ if (!Log.HasLoggedErrors) Log.LogError($"Failed to deserialize json from file {jsonFilePath}");
+ return false;
+ }
+ return true;
+ }
+ finally
+ {
+ if (file != null)
+ file.Dispose();
+ }
+ }
+
+ public async Task<JsonModelRoot?> GetJsonAsync(string jsonFilePath, FileStream file)
+ {
+ JsonModelRoot? json = null;
+ try
+ {
+ json = await JsonSerializer.DeserializeAsync<JsonModelRoot>(file, JsonOptions).ConfigureAwait(false);
+ }
+ catch (JsonException e)
+ {
+ Log.LogError($"Failed to deserialize json from file '{jsonFilePath}', JSON Path: {e.Path}, Line: {e.LineNumber}, Position: {e.BytePositionInLine}");
+ Log.LogErrorFromException(e, showStackTrace: false, showDetail: true, file: null);
+ }
+ return json;
+ }
+
+ internal void LogParsedJson (JsonModelRoot json)
+ {
+ if (json.Properties != null)
+ {
+ Log.LogMessage(MessageImportance.Low, "json has properties: ");
+ foreach (var property in json.Properties)
+ {
+ Log.LogMessage(MessageImportance.Low, $" {property.Key} = {property.Value}");
+ }
+ }
+ if (json.Items != null)
+ {
+ Log.LogMessage(MessageImportance.Low, "items: ");
+ foreach (var item in json.Items)
+ {
+ Log.LogMessage(MessageImportance.Low, $" {item.Key} = [");
+ foreach (var value in item.Value)
+ {
+ Log.LogMessage(MessageImportance.Low, $" {value.Identity}");
+ if (value.Metadata != null)
+ {
+ Log.LogMessage(MessageImportance.Low, " and some metadata, too");
+ }
+ }
+ Log.LogMessage(MessageImportance.Low, " ]");
+ }
+ }
+ }
+
+ public object? GetPropertyValue(TaskPropertyInfo property)
+ {
+ bool isItem = false;
+ if (typeof(ITaskItem[]).IsAssignableFrom(property.PropertyType))
+ {
+ if (_logDebugTask) Log.LogMessage(MessageImportance.Low, "GetPropertyValue called with @({0})", property.Name);
+ isItem = true;
+ }
+ else
+ {
+ if (_logDebugTask) Log.LogMessage(MessageImportance.Low, "GetPropertyValue called with $({0})", property.Name);
+ }
+ if (!isItem)
+ {
+ if (jsonModel?.Properties != null && jsonModel.Properties.TryGetValue(property.Name, out var value))
+ {
+ return value;
+ }
+ Log.LogError("Property {0} not found in {1}", property.Name, jsonFilePath);
+ throw new Exception();
+ }
+ else
+ {
+ if (jsonModel?.Items != null && jsonModel.Items.TryGetValue(property.Name, out var itemModels))
+ {
+ return ConvertItems(itemModels);
+ }
+
+ }
+ return null;
+ }
+
+ public static ITaskItem[] ConvertItems(JsonModelItem[] itemModels)
+ {
+ var items = new ITaskItem[itemModels.Length];
+ for (int i = 0; i < itemModels.Length; i++)
+ {
+ var itemModel = itemModels[i];
+ var item = new TaskItem(itemModel.Identity);
+ if (itemModel.Metadata != null)
+ {
+ // assume Identity key was already removed in JsonModelItem
+ foreach (var metadata in itemModel.Metadata)
+ {
+ item.SetMetadata(metadata.Key, metadata.Value);
+ }
+ }
+ items[i] = item;
+ }
+ return items;
+ }
+
+ public void SetPropertyValue(TaskPropertyInfo property, object? value)
+ {
+ if (_logDebugTask) Log.LogMessage(MessageImportance.Low, "SetPropertyValue called with {0}", property.Name);
+ if (property.Name == "JsonFilePath")
+ {
+ jsonFilePath = (string)value!;
+ }
+ else
+ throw new Exception($"JsonToItemsTask {TaskName} cannot set property {property.Name}");
+ }
+
+ }
+
+ public class JsonModelRoot
+ {
+ [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))]
+ public Dictionary<string, string>? Properties {get; set;}
+ public Dictionary<string, JsonModelItem[]>? Items {get; set;}
+
+ public JsonModelRoot() {}
+ }
+
+ [JsonConverter(typeof(JsonModelItemConverter))]
+ public class JsonModelItem
+ {
+ public string Identity {get;}
+ // n.b. will be deserialized case insensitive
+ public Dictionary<string, string>? Metadata {get;}
+
+ public JsonModelItem(string identity, Dictionary<string, string>? metadata)
+ {
+ Identity = identity;
+ Metadata = metadata;
+ }
+ }
+
+ public class CaseInsensitiveDictionaryConverter : JsonConverter<Dictionary<string, string>>
+ {
+ public override Dictionary<string, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(ref reader, options);
+ if (dict == null)
+ return null!;
+ return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
+ }
+ public override void Write(Utf8JsonWriter writer, Dictionary<string, string>? value, JsonSerializerOptions options) =>
+ JsonSerializer.Serialize(writer, value, options);
+ }
+ public class JsonModelItemConverter : JsonConverter<JsonModelItem>
+ {
+ public JsonModelItemConverter() {}
+
+ public override JsonModelItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.String:
+ var stringItem = reader.GetString();
+ if (string.IsNullOrEmpty(stringItem))
+ throw new JsonException ("deserialized json string item was null or the empty string");
+ return new JsonModelItem(stringItem!, metadata: null);
+ case JsonTokenType.StartObject:
+ var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(ref reader, options);
+ if (dict == null)
+ return null!;
+ var idict = new Dictionary<string, string> (dict, StringComparer.OrdinalIgnoreCase);
+ if (!idict.TryGetValue("Identity", out var identity) || string.IsNullOrEmpty(identity))
+ throw new JsonException ("deserialized json dictionary item did not have a non-empty Identity metadata");
+ else
+ idict.Remove("Identity");
+ return new JsonModelItem(identity, metadata: idict);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+ public override void Write(Utf8JsonWriter writer, JsonModelItem value, JsonSerializerOptions options)
+ {
+ if (value.Metadata == null)
+ JsonSerializer.Serialize(writer, value.Identity);
+ else
+ JsonSerializer.Serialize(writer, value.Metadata); /* assumes Identity is in there */
+ }
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks>$(TargetFrameworkForNETCoreTasks);$(TargetFrameworkForNETFrameworkTasks)</TargetFrameworks>
+ <OutputType>Library</OutputType>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
+ <Nullable>enable</Nullable>
+ <!-- Ignore nullable warnings on net4* -->
+ <!-- NoWarn Condition="$(TargetFramework.StartsWith('net4'))">$(NoWarn),CS8604,CS8602</NoWarn -->
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
+ <PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
+ <PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
+ <PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
+ <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="JsonToItemsTaskFactory.cs" />
+ </ItemGroup>
+
+ <!-- GetFilesToPackage assists to place `JsonToItemsTaskFactory.dll` in a NuGet package in Microsoft.NET.Runtime.MonoTargets.Sdk.pkgproj for external use -->
+ <Target Name="GetFilesToPackage" Returns="@(FilesToPackage)">
+ <ItemGroup>
+ <_PublishFramework Remove="@(_PublishFramework)" />
+ <_PublishFramework Include="$(TargetFrameworks)" />
+
+ <FilesToPackage Include="$(OutputPath)%(_PublishFramework.Identity)\$(AssemblyName).dll" TargetPath="tasks\%(_PublishFramework.Identity)" />
+ </ItemGroup>
+ </Target>
+
+</Project>
--- /dev/null
+# JsonToItemsTaskFactory
+
+A utility for reading json blobs into MSBuild items and properties.
+
+## Json blob format
+
+The json data must be a single toplevel dictionary with a `"properties"` or an `"items"` key (both are optional).
+
+The `"properties"` value must be a dictionary with more string values. The keys are case-insensitive and duplicates are not allowed.
+
+The `"items"` value must be an array of either string or dictionary elements (or a mix of both).
+String elements use the string value as the `Identity`.
+Dictionary elements must have strings as values, and must include an `"Identity"` key, and as many other metadata key/value pairs as desired. This dictionary is also case-insensitive and duplicate metadata keys are also not allowed.
+
+#### Example
+
+```json
+{
+ "properties": {
+ "x1": "val1",
+ "X2": "val2",
+ },
+ "items" : {
+ "FunFiles": ["funFile1.txt", "funFile2.txt"],
+ "FilesWithMeta": [{"identity": "funFile3.txt", "TargetPath": "bin/fun3"},
+ "funFile3.and.a.half.txt",
+ {"identity": "funFile4.txt", "TargetPath": "bin/fun4"}]
+ }
+}
+```
+
+## UsingTask and Writing Targets
+
+To use the task, you need to reference the assembly and add the task to the project, as well as declare the task parameters that correspond to the properties and items you want to retrieve from the json blob.
+
+```xml
+<UsingTask TaskName="MyJsonReader" AssemblyFile="..\JsonToItemsTaskFactory\bin\Debug\net6.0\JsonToItemsTaskFactory.dll"
+ TaskFactory="JsonToItemsTaskFactory.JsonToItemsTaskFactory">
+ <ParameterGroup>
+ <X1 ParameterType="System.String" Required="false" Output="true" />
+ <FunFiles ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ <FilesWithMeta ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
+ </ParameterGroup>
+</UsingTask>
+```
+
+The parameter group parameters are all optional. They must be non-required outputs of type `System.String` or `Microsoft.Build.Framework.ITaskItem[]`. The former declares properties to capture from the file, while the latter declares item lists.
+
+The above declares a task `MyJsonReader` which will be used to retries the `X1` property and the `FunFiles` and `FilesWithMeta` items.
+
+To use the task, a `JsonFilePath` attribute specifies the file to read.
+
+```xml
+<Target Name="RunMe">
+ <MyJsonReader JsonFilePath="$(MSBuildThisFileDirectory)\example.jsonc">
+ <Output TaskParameter="X1" PropertyName="FromJsonX1" />
+ <Output TaskParameter="FunFiles" ItemName="FromJsonFunFiles" />
+ <Output TaskParameter="FilesWithMeta" ItemName="FromJsonWithMeta" />
+ </MyJsonReader>
+ <Message Importance="High" Text="X1 = $(FromJsonX1)" />
+ <Message Importance="High" Text="FunFiles = @(FromJsonFunFiles)" />
+ <ItemGroup>
+ <FromJsonWithMetaWithTP Include="@(FromJsonWithMeta->HasMetadata('TargetPath'))" />
+ <FromJsonWithMetaWithoutTP Include="@(FromJsonWithMeta)" Exclude="@(FromJsonWithMetaWithTP)" />
+ </ItemGroup>
+ <Message Importance="High" Text="FilesWithMeta = @(FromJsonWithMetaWithTP) TargetPath='%(TargetPath)'"/>
+ <Message Importance="High" Text="FilesWithMeta = @(FromJsonWithMetaWithoutTP) (No TargetPath)" />
+</Target>
+```
+
+When the target `RunMe` runs, the task will read the json file and populate the outputs. Running the target, the output will be:
+
+```console
+$ dotnet build Example
+ X1 = val1
+ FunFiles = funFile1.txt;funFile2.txt
+ FilesWithMeta = funFile3.txt TargetPath='bin/fun3'
+ FilesWithMeta = funFile4.txt TargetPath='bin/fun4'
+ FilesWithMeta = funFile3.and.a.half.txt (No TargetPath)
+
+Build succeeded.
+ 0 Warning(s)
+ 0 Error(s)
+Time Elapsed 00:00:00.15
+```
+