diff --git a/.gitattributes b/.gitattributes index e53d317832e..36d15129144 100644 --- a/.gitattributes +++ b/.gitattributes @@ -47,3 +47,17 @@ ############################################################################### *.png binary *.snk binary + + +############################################################################### +# Set file behavior to: +# treat as text, lf line endings +############################################################################### +changelog text eol=lf +control text eol=lf +copyright text eol=lf +rules text eol=lf +format text eol=lf +compat text eol=lf +*.bats text eol=lf +*.1 text eol=lf diff --git a/Arcade.sln b/Arcade.sln index aadab3b7434..42e58afe3b5 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -99,6 +99,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Helix.Sdk. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.AsmDiff", "src\Microsoft.DotNet.AsmDiff\Microsoft.DotNet.AsmDiff.csproj", "{FDCE3109-3EA0-4166-B6C0-BE7BB4196864}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SharedFramework.Sdk", "src\Microsoft.DotNet.SharedFramework.Sdk\Microsoft.DotNet.SharedFramework.Sdk.csproj", "{9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Tasks.Installers", "src\Microsoft.DotNet.Build.Tasks.Installers\Microsoft.DotNet.Build.Tasks.Installers.csproj", "{32070F78-5045-4402-BCB5-D8A2D479770A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.RemoteExecutor.Tests", "src\Microsoft.DotNet.RemoteExecutor\tests\Microsoft.DotNet.RemoteExecutor.Tests.csproj", "{D6AC20A4-1719-49FE-B112-B2AB564496F8}" @@ -113,6 +115,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProject EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttributeDifference.Contract", "src\Microsoft.DotNet.ApiCompat\tests\TestProjects\AttributeDifference\Contract\AttributeDifference.Contract.csproj", "{1CC55B23-6212-4120-BF52-8DED9CFF9FBC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Tasks.Archives", "src\Microsoft.DotNet.Build.Tasks.Archives\Microsoft.DotNet.Build.Tasks.Archives.csproj", "{5579768A-CC07-477C-ACE4-06FE9B0686A7}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SourceBuild.Tasks", "src\Microsoft.DotNet.SourceBuild\tasks\Microsoft.DotNet.SourceBuild.Tasks.csproj", "{F9D72AF5-9320-43C8-A24F-CBE294FCED0A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SourceBuild.Tasks.Tests", "src\Microsoft.DotNet.SourceBuild\tests\Microsoft.DotNet.SourceBuild.Tasks.Tests.csproj", "{CE5278A3-2442-4309-A543-5BA5C1C76A2A}" @@ -639,6 +643,18 @@ Global {FDCE3109-3EA0-4166-B6C0-BE7BB4196864}.Release|x64.Build.0 = Release|Any CPU {FDCE3109-3EA0-4166-B6C0-BE7BB4196864}.Release|x86.ActiveCfg = Release|Any CPU {FDCE3109-3EA0-4166-B6C0-BE7BB4196864}.Release|x86.Build.0 = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|x64.ActiveCfg = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|x64.Build.0 = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|x86.ActiveCfg = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Debug|x86.Build.0 = Debug|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|Any CPU.Build.0 = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|x64.ActiveCfg = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|x64.Build.0 = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|x86.ActiveCfg = Release|Any CPU + {9EB0B2AE-C55C-4DEF-90B3-44E73F0A366D}.Release|x86.Build.0 = Release|Any CPU {32070F78-5045-4402-BCB5-D8A2D479770A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32070F78-5045-4402-BCB5-D8A2D479770A}.Debug|Any CPU.Build.0 = Debug|Any CPU {32070F78-5045-4402-BCB5-D8A2D479770A}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -699,6 +715,18 @@ Global {1CC55B23-6212-4120-BF52-8DED9CFF9FBC}.Release|x64.Build.0 = Release|Any CPU {1CC55B23-6212-4120-BF52-8DED9CFF9FBC}.Release|x86.ActiveCfg = Release|Any CPU {1CC55B23-6212-4120-BF52-8DED9CFF9FBC}.Release|x86.Build.0 = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|x64.Build.0 = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Debug|x86.Build.0 = Debug|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|Any CPU.Build.0 = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|x64.ActiveCfg = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|x64.Build.0 = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|x86.ActiveCfg = Release|Any CPU + {5579768A-CC07-477C-ACE4-06FE9B0686A7}.Release|x86.Build.0 = Release|Any CPU {F9D72AF5-9320-43C8-A24F-CBE294FCED0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F9D72AF5-9320-43C8-A24F-CBE294FCED0A}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9D72AF5-9320-43C8-A24F-CBE294FCED0A}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj b/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj new file mode 100644 index 00000000000..5b3de6dc143 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/Microsoft.DotNet.Build.Tasks.Archives.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + false + + true + false + + Targets for producing an archive of file outputs. + MSBuildSdk + + false + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/README.md b/src/Microsoft.DotNet.Build.Tasks.Archives/README.md new file mode 100644 index 00000000000..e85298c3091 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/README.md @@ -0,0 +1,12 @@ +# Microsoft.DotNet.Build.Tasks.Archives + +Targets package for producing zip and tar archives. + +This package generates an archive that can be extracted on top of an existing .NET SDK or Runtime layout. The name of this file is derived from the `ArchiveName` property and the RID. The `ArchiveName` property defaults to the project file name without the extension. This package calls the `PublishToDisk` target on the project to generate the project layout. + +## Build Skip Support for Unsupported Platforms and Servicing + +This SDK also supports automatically skipping builds on unsupported platforms or in servicing releases. If a project with a list of provided RIDs in `RuntimeIdentifiers` is built with the `RuntimeIdentifier` property set to a RID that is not in the `RuntimeIdentifiers` list, the build will be skipped. This enables cleanly skipping optional packs, installers, or bundles that only exist on specific platforms. + +Additionally, if a `ProjectServicingConfiguration` item is provided with the identity of the project name and the `PatchVersion` metadata on the item is not equal to the current `PatchVersion`, the build will be skipped. This support enables a repository to disable building targeting packs in servicing releases if that is desired. + diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.targets b/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.targets new file mode 100644 index 00000000000..3378424536a --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/build/Microsoft.DotNet.Build.Tasks.Archives.targets @@ -0,0 +1,7 @@ + + + $(MSBuildThisFileDirectory) + + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets b/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets new file mode 100644 index 00000000000..5146545b9f2 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Archives/build/archives.targets @@ -0,0 +1,121 @@ + + + + + + + + + + + %(CurrentProjectServicingConfiguration.PatchVersion) + $(MostRecentProducedServicingPatchVersion) + + + + + + + true + + + + <_TargetRuntimeIdentifiers Include="$(RuntimeIdentifiers)" /> + + + + + <_RidInRidList Condition="'%(_TargetRuntimeIdentifiers.Identity)' == '$(RuntimeIdentifier)'">true + true + + + true + + + + + $([System.IO.Path]::GetFileNameWithoutExtension($(MSBuildProjectFile))) + $(ArchiveName)-symbols + + + + + <_OutputPathRoot>$(IntermediateOutputPath)output/ + <_ArchiveFileName>$(ArchiveName)-$(Version) + <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(ArchiveName)-$(RuntimeIdentifier)-$(Version) + + + + + + + + + + + <_SymbolsOutputPathRoot>$(IntermediateOutputPath)symbols/ + <_ArchiveFileName>$(SymbolsArchiveName)-$(Version) + <_ArchiveFileName Condition="'$(RuntimeIdentifier)' != ''">$(SymbolsArchiveName)-$(RuntimeIdentifier)-$(Version) + + + + + + + + + + + $(BuildDependsOn); + _GetSkipArchivesBuildProps; + _CreateArchive; + _CreateSymbolsArchive + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj index 663078649ef..152dd84b8c3 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/Microsoft.DotNet.Build.Tasks.Installers.csproj @@ -9,12 +9,22 @@ Installer task package Arcade Build Tool Installer true + $(NoWarn);NU5127 + false + + + + + + + build + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/README.md b/src/Microsoft.DotNet.Build.Tasks.Installers/README.md index a5162d18379..0df8082eb06 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/README.md +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/README.md @@ -1,6 +1,93 @@ # Microsoft.DotNet.Build.Tasks.Installers -Task package for installer specific tasks. Currently contains: +Task package for installer specific tasks with optional opt-in targets for automatic installer generation. + +This package supports generating installers for projects in .msi, .deb, .rpm, and .pkg formats. This support is opt-in by setting the `GenerateInstallers` property to `true`. In addition, to enable the .deb and .rpm support, the `GenerateDebPackage` and `GenerateRpmPackage` properties respectively need to be set to true. This support depends on a `PublishToDisk` target that publishes the project's outputs to the `$(OutputPath)` folder. + +There are a few common properties used by all of the installer types and the bundle installers (documented after the installers): + +- `InstallerName` + - The name of the installer file without an extension.. +- `ProductBrandPrefix` + - The branding name of this component, such as "Microsoft Windows Desktop" +- `PackageBrandNameSuffix` (`ToolPack` installers only) + - The type of package, for example "Shared Host" or "AppHost Pack". This is set automatically for any non-ToolPack package types. + +For correct branding and versioning, this SDK has a dependency on Arcade's versioning setup, including the `MajorVersion`, `MinorVersion`, `PreReleaseVersionLabel`, `PreReleaseVersionIteration` and `DotNetFinalVersionKind` properties. + +Since some framework packs do not use `RuntimeIdentifier`s in their build, for example targeting packs, for the installer build you can define `InstallerRuntimeIdentifiers` and the build will fan out for the installer build across those target RIDs. In addition, the `InstallerRuntimeIdentifier` property will default to the value of `RuntimeIdentifier` if it is not set. + +### Wix MSI configuration + +If you have files that need to have a stabilized identity in the MSI file, you can add items to the `HeatOutputFileElementToStabilize` item group. Each item in this group specifies a unique suffix of a path (enough to identify a single file) and a value for the `ReplacementId` metadata as the id to set in the MSI for this file. + +If you want to create MSIs for the target RID that target other architecture install locations, you can add `CrossArchSdkMsiInstallerArch` items for all of the target architecture install locations you want to generate installers of the current build for. + +### Linux package configuration + +To add package dependencies for linux packages, add a `LinuxPackageDependency` item with the version in the `Version` metadata. + +#### Deb package configuration + +To add additional properties for the deb package tool, add items to the `DebJsonProperty` item group. + +#### Rpm package configuration + +To add additional properties for the deb package tool, add items to the `RpmJsonProperty` item group. + +### MacOS Pkg configuration + +To specify a directory where `pkgbuild` should look for scripts, set the `MacOSScriptsDirectory` to the path to the scripts. + +If you are building a `ToolPack` pack, you need to specify the `MacOSComponentNamePackType` property to create the component name for the `.pkg` package. If you want the component name to not include the version (for example you are building the shared host), you can set the `IncludeVersionInMacOSComponentName` property to false. + +If your `pkg` is later going to be bundled in a macOS `pkg` bundle created by `productbuild`, you should also specify the `MacOSPackageDescription` property, which will set the package description in the bundle distribution file. + +### Visual Studio Insertion Package configuration + +Visual Studio insertion packages generated by the SDK are named in the form `VS.Redist.Common.$(VSInsertionShortComponentName).$(InstallerTargetArchitecture)$(CrossArchContentsBuildPart).$(MajorVersion).$(MinorVersion)` + +The `InstallerTargetArchitecture` and later properties are automatically calculated by the SDK or are version properties. You are required to provide the `VSInsertionShortComponentName` property value yourself. + +## Building Installer Bundles + +As part of the installer support, the SDK supports building Wix bundle installers and macOS pkg bundles. These bundles are defined in a separate project with a `.bundleproj` extension that includes the .NET SDK and this SDK as shown in the example project. Like the installer targets, the bundle targets also use the `InstallerName` property and the support for `RuntimeIdentifiers` to identify target architectures for the bundle. The bundle targets verify that the RID is targeting an OS that supports an installer bundle, so be sure to limit the RID list to platforms that support bundles (Windows and macOS today). + +The bundle installers also require the `ProductBrandPrefix` property and a `BundleNameSuffix` property in the place of the `PackageBrandNameSuffix` property used by the installer targets. + +To include installers in the bundle, add `BundleComponentReference` items that point to the projects that produce the installers. + +### Wix Bundle configuration + +A number of properties are required to configure the Wix bundle generation: + +- `BundleInstallerUpgradeCodeSeed` + - This property specifies a seed for generating the bundle upgrade GUID. This must not change within a product band, otherwise the upgrade code will not match. +- `BundleThemeDirectory` + - This property should point to a directory that contains the following files and folders: + - `bundle.thm` + - `bundle.wxl` + - A `theme` directory that contains subdirectories with resources per codepage. + +### MacOS Pkg bundle configuration + +A number of properties are required to configure the MacOS Pkg bundle generation: + +- `MacOSBundleIdentifierName` + - The identifier of the pkg bundle. +- `MacOSBundleResourcesPath` + - The folder with the resources for the bundle, including various HTML and RTF files that the installer shows the user. +- `MacOSBundleTemplate` + - A partial [macOS Distribution XML file](https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html). The build system will add the ``, `<choices-outline>`, `<choice>`, and `<pkg-ref>` elements automatically to the provided XML file based on the provided properties and the bundled `.pkg` files defined in the project file. + +## Build Skip Support for Unsupported Platforms and Servicing + +This SDK also supports automatically skipping builds on unsupported platforms or in servicing releases. If a project with a list of provided RIDs in `RuntimeIdentifiers` or `InstallerRuntimeIdentifiers` is built with the `RuntimeIdentifier` property or `InstallerRuntimeIdentifier` property set to a RID that is not in the `RuntimeIdentifiers` or `InstallerRuntimeIdentifiers` list respectively, the build will be skipped. This enables cleanly skipping optional packs, installers, or bundles that only exist on specific platforms. + +Additionally, if a `ProjectServicingConfiguration` item is provided with the identity of the project name and the `PatchVersion` metadata on the item is not equal to the current `PatchVersion`, the build will be skipped. This support enables a repository to disable building targeting packs in servicing releases if that is desired. + +## Tasks + - **CreateLightCommandPackageDrop** - Create a layout that can be used to re-execute a light command. This can be used during post-build signing after files have been replaced. A cmd file that can be used to reconstruct the installer is created. diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props index 39d57d1b551..86072785333 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.props @@ -2,7 +2,8 @@ <PropertyGroup> <MicrosoftDotNetBuildTasksInstallersTaskAssembly Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\Microsoft.DotNet.Build.Tasks.Installers.dll</MicrosoftDotNetBuildTasksInstallersTaskAssembly> <MicrosoftDotNetBuildTasksInstallersTaskAssembly Condition="'$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tools\net472\Microsoft.DotNet.Build.Tasks.Installers.dll</MicrosoftDotNetBuildTasksInstallersTaskAssembly> + <MicrosoftDotNetBuildTasksInstallersMSBuildDir Condition="'$(MicrosoftDotNetBuildTasksInstallersMSBuildDir)' == ''">$(MSBuildThisFileDirectory)</MicrosoftDotNetBuildTasksInstallersMSBuildDir> </PropertyGroup> - <UsingTask TaskName="CreateLightCommandPackageDrop" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <Import Project="$(MicrosoftDotNetBuildTasksInstallersMSBuildDir)installer.props" /> </Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.targets new file mode 100644 index 00000000000..1d307053518 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/Microsoft.DotNet.Build.Tasks.Installers.targets @@ -0,0 +1,3 @@ +<Project> + <Import Project="$(MicrosoftDotNetBuildTasksInstallersMSBuildDir)installer.targets" /> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.props b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.props new file mode 100644 index 00000000000..6321d35eb92 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.props @@ -0,0 +1,7 @@ +<Project> + + <!-- Set up build infra for the acquire-* projects. --> + + <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" /> + +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.targets new file mode 100644 index 00000000000..a981e33d3dc --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/Directory.Build.targets @@ -0,0 +1,12 @@ +<Project> + + <!-- Set up build infra for the acquire-* projects. --> + + <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" /> + + <PropertyGroup> + <GenerateInstallers>true</GenerateInstallers> + </PropertyGroup> + + <Import Project="$(MSBuildThisFileDirectory)..\installer.singlerid.targets" Condition="'$(_InstallerTargetsImported)' != 'true'" /> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj new file mode 100644 index 00000000000..adbe84df1f8 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-nuget-exe/acquire-nuget-exe.proj @@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <UsingTask TaskName="DownloadFile" AssemblyFile="$(ArcadeSdkBuildTasksAssembly)" /> + + <!-- + Acquire NuGet.exe, if not present. + --> + <Target Name="AcquireNuGetExeCore" + Condition="!Exists('$(NuGetExeFile)')" + DependsOnTargets="_GetAcquireNuGetExeProperties"> + <MakeDir Directories="$(NuGetExeToolDir)" /> + <DownloadFile + Uri="$(NuGetExeDownloadUrl)" + DestinationPath="$(NuGetExeFile)" + Overwrite="true" /> + </Target> + +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj new file mode 100644 index 00000000000..48f58f1bc71 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/acquisition/acquire-wix/acquire-wix.proj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <UsingTask TaskName="DownloadFile" AssemblyFile="$(ArcadeSdkBuildTasksAssembly)" /> + + <!-- + Acquire WiX tools, if not present. + + Adapted from https://github.com/dotnet/core-sdk/blob/6aed0cd3614f9b740cfb3f21fdb795bab53ef7e9/src/redist/targets/GenerateMSIs.targets#L80-L102 + --> + <Target Name="AcquireWixCore" + DependsOnTargets="GetAcquireWixProperties" + Inputs="$(WixDownloadSentinel)" + Outputs="$(WixDestinationPath)"> + <!-- Setup sentinel to take advantage of incrementality --> + <MakeDir Directories="$(WixToolsDir)" /> + <WriteLinesToFile + File="$(WixDownloadSentinel)" + Lines="$(WixVersion)" + Overwrite="true" + Encoding="Unicode" /> + + <DownloadFile + Uri="$(WixDownloadUrl)" + DestinationPath="$(WixDestinationPath)" + Overwrite="true" /> + + <Unzip + SourceFiles="$(WixDestinationPath)" + DestinationFolder="$(WixToolsDir)" /> + </Target> + +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/bundle.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/bundle.targets new file mode 100644 index 00000000000..cc51f13f22d --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/bundle.targets @@ -0,0 +1,164 @@ +<Project> + <Target Name="GetBundleGenerationFlags" DependsOnTargets="_GetTargetOSArchInfo" Condition="'$(SkipInstallerBuild)' != 'true'"> + <Error + Text="Unable to build a bundle installer for a non-Windows, non-macOS platform." + Condition="'$(TargetRuntimeOS)' != 'win' and '$(TargetRuntimeOS)' != 'osx'" /> + <!-- Filter the installer generation/build flags for the current build machine. --> + <PropertyGroup> + <GenerateExeBundle Condition="'$(_osArchSupportsWixBasedInstallers)' == 'true'">true</GenerateExeBundle> + <GeneratePkgBundle Condition="$([MSBuild]::IsOSPlatform('osx')) and '$(TargetRuntimeOS)' == 'osx'">true</GeneratePkgBundle> + </PropertyGroup> + </Target> + + <Import Project="$(MSBuildThisFileDirectory)wix/wix.targets" /> + + <Target Name="GenerateBundles" DependsOnTargets="GetBundleGenerationFlags" Condition="'$(SkipInstallerBuild)' != 'true'"> + <ItemGroup> + <_InstallerBuildProject Include="$(MSBuildProjectFile)" + Targets="GenerateExeBundle" + Properties="GenerateExeBundle=true" + Condition="'$(GenerateExeBundle)' == 'true'" /> + <_InstallerBuildProject Include="$(MSBuildProjectFile)" + Targets="GeneratePkgBundle" + Properties="GeneratePkgBundle=true" + Condition="'$(GeneratePkgBundle)' == 'true'" /> + </ItemGroup> + <MSBuild Projects="@(_InstallerBuildProject)" + Targets="%(_InstallerBuildProject.Targets)" + BuildInParallel="$(BuildInParallel)" /> + </Target> + + <Target Name="GeneratePkgBundle" DependsOnTargets="CreatePkgBundle" /> + <Target Name="GenerateExeBundle" DependsOnTargets="CreateWixInstaller" /> + + <!-- + Set up properties for installer generation. Project-type-specific properties are set up in + targets that use BeforeTargets="_GetInstallerProperties". + --> + <Target Name="_GetInstallerProperties" + DependsOnTargets="_GetTargetOSArchInfo; + _GetProductBrandName"> + <PropertyGroup> + <InstallerName Condition="'$(InstallerName)' == ''">$(ArchiveName)</InstallerName> + </PropertyGroup> + + <Error + Text="InstallerName '$(InstallerName)' is empty or starts with a '-': expected a value like 'dotnet-runtime'." + Condition="'$(InstallerName)' == '' or $(InstallerName.StartsWith('-'))" /> + + <PropertyGroup> + <VersionedInstallerName>$(InstallerName)-$(MajorVersion).$(MinorVersion)</VersionedInstallerName> + <VersionedInstallerName>$(VersionedInstallerName.ToLowerInvariant())</VersionedInstallerName> + <InstallerPackageRelease>1</InstallerPackageRelease> + <InstallerPackageVersion>$(VersionPrefix)</InstallerPackageVersion> + </PropertyGroup> + + <PropertyGroup> + <InstallerExtension Condition="'$(TargetRuntimeOS)' == 'win'">.exe</InstallerExtension> + <InstallerExtension Condition="'$(TargetRuntimeOS)' == 'osx'">.pkg</InstallerExtension> + </PropertyGroup> + + <PropertyGroup> + <_InstallerIntermediatesDir>$(IntermediateOutputPath)$(InstallerName)/$(InstallerPackageVersion)/</_InstallerIntermediatesDir> + + <InstallerBuildPart>$(Version)-$(TargetRuntimeOS)-$(InstallerTargetArchitecture)</InstallerBuildPart> + + <!-- Location to place the installer, in artifacts. --> + <InstallerFileNameWithoutExtension>$(InstallerName)-$(InstallerBuildPart)</InstallerFileNameWithoutExtension> + <_InstallerFile Condition="'$(_InstallerFile)' == ''">$(PackageOutputPath)$(InstallerFileNameWithoutExtension)$(InstallerExtension)</_InstallerFile> + <ExeBundleInstallerEngineFile>$(PackageOutputPath)$(InstallerFileNameWithoutExtension)-engine.exe</ExeBundleInstallerEngineFile> + </PropertyGroup> + </Target> + + <Target Name="_GetBundledComponentInstallers"> + <ItemGroup Condition="'$(GenerateExeBundle)' == 'true'"> + <BundleComponentReference> + <Targets>CreateWixInstaller</Targets> + <AdditionalProperties>GenerateMSI=true</AdditionalProperties> + <RemoveProperties>GenerateExeBundle</RemoveProperties> + </BundleComponentReference> + </ItemGroup> + <ItemGroup Condition="'$(GeneratePkgBundle)' == 'true'"> + <BundleComponentReference> + <Targets>CreatePkg</Targets> + <AdditionalProperties>GeneratePkg=true</AdditionalProperties> + <RemoveProperties>GeneratePkgBundle</RemoveProperties> + </BundleComponentReference> + </ItemGroup> + + <MSBuild Projects="@(BundleComponentReference)" Targets="%(Targets)" RemoveProperties="%(RemoveProperties)"> + <Output TaskParameter="TargetOutputs" ItemName="_BundledComponents" /> + </MSBuild> + </Target> + + <!-- + Create MSI installer, using WiX tools. + --> + <Target Name="CreateWixInstaller" + DependsOnTargets=" + _GetInstallerProperties; + RunLightLinker" + Returns="$(_OutInstallerFile)"> + <Message Text="$(MSBuildProjectName) -> $(_OutInstallerFile)" Importance="high" /> + </Target> + + <!-- + Create macOS pkg installer. + --> + <UsingTask TaskName="GenerateMacOSDistributionFile" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)"/> + <Target Name="CreatePkgBundle" + DependsOnTargets=" + _GetInstallerProperties; + _GetBundledComponentInstallers" + Returns="$(_InstallerFile)"> + + <Error Text="A template XML file for the bundle must be provided via the MacOSBundleTemplate property" + Condition="'$(MacOSBundleTemplate)' == ''" /> + <Error Text="A bundle identifier must be provided via the MacOSBundleIdentifierName property" + Condition="'$(MacOSBundleIdentifierName)' == ''" /> + + <PropertyGroup> + <_MacOSIntermediatesPath>$(IntermediateOutputPath)macos/</_MacOSIntermediatesPath> + <_MacOSDistributionFile>$(_MacOSIntermediatesPath)distribution-file.xml</_MacOSDistributionFile> + </PropertyGroup> + + <Copy SourceFiles="@(_BundledComponents)" + DestinationFolder="$(_MacOSIntermediatesPath)" + SkipUnchangedFiles="true" + OverwriteReadOnlyFiles="true"> + <Output TaskParameter="DestinationFiles" ItemName="_MacOSPackagesToBundle" /> + </Copy> + + <ItemGroup> + <_MacOSPackagesToBundle> + <FileNameWithExtension>%(FileName)%(Extension)</FileNameWithExtension> + </_MacOSPackagesToBundle> + </ItemGroup> + + <GenerateMacOSDistributionFile + TemplatePath="$(MacOSBundleTemplate)" + ProductBrandName="$(ProductBrandName)" + TargetArchitecture="$(TargetArchitecture)" + BundledPackages="@(_MacOSPackagesToBundle)" + DestinationFile="$(_MacOSDistributionFile)" /> + + <PropertyGroup> + <_pkgArgs></_pkgArgs> + <_pkgArgs Condition="'$(MacOSBundleResourcesPath)' == ''">$(_pkgArgs) --resources $(MacOSBundleResourcesPath)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --identifier $(MacOSBundleIdentifierName)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --version $(ProductVersion)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --package-path $(_MacOSIntermediatesPath)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --distribution $(_MacOSDistributionFile)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) $(_InstallerFile)</_pkgArgs> + </PropertyGroup> + + <Exec Command="productbuild $(_pkgArgs)" /> + + + <Message Text="$(MSBuildProjectName) -> $(_InstallerFile)" Importance="high" /> + </Target> + + <PropertyGroup> + <BuildDependsOn>$(BuildDependsOn);_GetSkipInstallerBuildProps;GenerateBundles</BuildDependsOn> + </PropertyGroup> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/compat b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/compat new file mode 100644 index 00000000000..ec635144f60 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/compat @@ -0,0 +1 @@ +9 diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/source/format b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/source/format new file mode 100644 index 00000000000..163aaf8d82b --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_files/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_tool.sh b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_tool.sh new file mode 100644 index 00000000000..9e069b7a1e0 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/package_tool.sh @@ -0,0 +1,264 @@ +#!/usr/bin/env bash +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +## Load Functions ## +source $SCRIPT_DIR/scripts/debian_build_lib.sh + +## Debian Package Creation Functions ## +execute(){ + if ! parse_args_and_set_env_vars "$@"; then + exit 1 + fi + + # Exit if required validation fails + if ! validate_inputs; then + exit 1 + fi + + parse_config_and_set_env_vars + clean_or_create_build_dirs + package_all + generate_all + create_source_tarball + + # Actually Build Package Files + (cd ${PACKAGE_SOURCE_DIR}; debuild -us -uc) +} + +parse_args_and_set_env_vars(){ + OPTIND=1 # Reset in case getopts has been used previously in the shell. + + while getopts ":n:v:i:o:h" opt; do + case $opt in + n) + export PACKAGE_NAME="$OPTARG" + ;; + v) + export PACKAGE_VERSION="$OPTARG" + ;; + i) + export INPUT_DIR="$OPTARG" + ;; + o) + export OUTPUT_DIR="$OPTARG" + ;; + h) + print_help + return 1 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + return 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + return 1 + ;; + esac + done + + # Special Input Directories + Paths + ABSOLUTE_PLACEMENT_DIR="${INPUT_DIR}/\$" + PACKAGE_ROOT_PLACEMENT_DIR="${INPUT_DIR}/package_root" + CONFIG="$INPUT_DIR/debian_config.json" + + return 0 +} + +print_help(){ + echo "Usage: package_tool [-i <INPUT_DIR>] [-o <OUTPUT_DIRECTORY>] + [-n <PACKAGE_NAME>] [-v <PACKAGE_VERSION>] [-h] + + REQUIRED: + -i <INPUT_DIR>: Input directory conforming to package_tool conventions and debian_config.json + -o <OUTPUT_DIR>: Output directory for debian package and other artifacts + + OPTIONAL: + -n <PACKAGE_NAME>: name of created package, will override value in debian_config.json + -v <PACKAGE_VERSION>: version of created package, will override value in debian_config.json + -h: Show this message + + NOTES: + See Readme for more information on package_tool conventions and debian_config.json format + https://github.com/dotnet/cli/tree/master/packaging/debian/package_tool + " +} + +validate_inputs(){ + local ret=0 + if [[ -z "$INPUT_DIR" ]]; then + echo "ERROR: -i <INPUT_DIRECTORY> Not Specified" + ret=1 + fi + + if [[ -z "$OUTPUT_DIR" ]]; then + echo "ERROR: -o <OUTPUT_DIRECTORY> Not Specified." + ret=1 + fi + + if [[ ! -d "$PACKAGE_ROOT_PLACEMENT_DIR" ]]; then + echo "ERROR: package_root directory does not exist" + echo $PACKAGE_ROOT_PLACEMENT_DIR + ret=1 + fi + + if [[ ! -f "$CONFIG" ]]; then + echo "ERROR: debian_config.json file does not exist" + echo $CONFIG + ret=1 + fi + + return $ret +} + +parse_config_and_set_env_vars(){ + extract_base_cmd="python $SCRIPT_DIR/scripts/extract_json_value.py" + + # Arguments Take Precedence over Config + [ -z "$PACKAGE_VERSION" ] && PACKAGE_VERSION="$($extract_base_cmd $CONFIG "release.package_version")" + [ -z "$PACKAGE_NAME" ] && PACKAGE_NAME="$($extract_base_cmd $CONFIG "package_name")" + + # Inputs + INPUT_SAMPLES_DIR="$INPUT_DIR/samples" + INPUT_DOCS_DIR="$INPUT_DIR/docs" + DOCS_JSON_PATH="$INPUT_DIR/docs.json" + + PACKAGE_SOURCE_DIR="${OUTPUT_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}" + + if ! INSTALL_ROOT="$($extract_base_cmd $CONFIG "install_root")"; then + INSTALL_ROOT="/usr/share/$PACKAGE_NAME" + fi + + DEBIAN_DIR="${PACKAGE_SOURCE_DIR}/debian" + DOCS_DIR="${PACKAGE_SOURCE_DIR}/docs" +} + +clean_or_create_build_dirs(){ + rm -rf ${PACKAGE_SOURCE_DIR} + mkdir -p $DEBIAN_DIR +} + +package_all(){ + package_static_files + package_package_root_placement + package_absolute_placement + package_samples + package_docs + package_install_scripts +} + +generate_all(){ + generate_config_templates + generate_manpages + generate_manpage_manifest + generate_sample_manifest + write_debian_install_file +} + +create_source_tarball(){ + rm -f ${OUTPUT_DIR}/${PACKAGE_NAME}_${PACKAGE_VERSION}.orig.tar.gz + tar -cvzf ${OUTPUT_DIR}/${PACKAGE_NAME}_${PACKAGE_VERSION}.orig.tar.gz -C $PACKAGE_SOURCE_DIR . +} + +## Packaging Functions ## +package_static_files(){ + cp -a $SCRIPT_DIR/package_files/debian/* ${PACKAGE_SOURCE_DIR}/debian +} + +package_package_root_placement(){ + add_dir_to_install ${PACKAGE_ROOT_PLACEMENT_DIR} "" +} + +package_absolute_placement(){ + if [[ -d "$ABSOLUTE_PLACEMENT_DIR" ]]; then + abs_in_package_dir="\$" + + add_dir_to_install ${ABSOLUTE_PLACEMENT_DIR} $abs_in_package_dir + + # Get List of all files in directory tree, relative to ABSOLUTE_PLACEMENT_DIR + abs_files=( $(_get_files_in_dir_tree $ABSOLUTE_PLACEMENT_DIR) ) + + # For each file add a a system placement + for abs_file in ${abs_files[@]} + do + parent_dir=$(dirname $abs_file) + filename=$(basename $abs_file) + + add_system_file_placement "$abs_in_package_dir/$abs_file" "/$parent_dir" + done + fi +} + +package_samples(){ + if [[ -d "$INPUT_SAMPLES_DIR" ]]; then + cp -a $INPUT_SAMPLES_DIR/. $PACKAGE_SOURCE_DIR + fi +} + +package_docs(){ + if [[ -d "$INPUT_DOCS_DIR" ]]; then + mkdir -p $DOCS_DIR + cp -a $INPUT_DOCS_DIR/. $DOCS_DIR + fi +} + +package_install_scripts(){ + # copy scripts for the package's control section like preinst, postint, etc + if [[ -d "$INPUT_DIR/debian" ]]; then + cp -a "$INPUT_DIR/debian/." $DEBIAN_DIR + fi +} + +## Generation Functions ## +generate_config_templates(){ + python ${SCRIPT_DIR}/scripts/config_template_generator.py $CONFIG $SCRIPT_DIR/templates/debian $DEBIAN_DIR $PACKAGE_NAME $PACKAGE_VERSION +} + +generate_manpages(){ + if [[ -f "$DOCS_JSON_PATH" ]]; then + mkdir -p $DOCS_DIR + + # Generate the manpages from json spec + python ${SCRIPT_DIR}/scripts/manpage_generator.py ${DOCS_JSON_PATH} ${DOCS_DIR} + fi +} + +generate_manpage_manifest(){ + # Get a list of files generated relative to $DOCS_DIR + generated_manpages=( $(_get_files_in_dir_tree $DOCS_DIR) ) + + # Get path relative to $PACKAGE_SOURCE_DIR to prepend to each filename + # This syntax is bash substring removal + docs_rel_path=${DOCS_DIR#${PACKAGE_SOURCE_DIR}/} + + # Remove any existing manifest + rm -f ${DEBIAN_DIR}/${PACKAGE_NAME}.manpages + + for manpage in ${generated_manpages[@]} + do + echo "${docs_rel_path}/${manpage}" >> "${DEBIAN_DIR}/${PACKAGE_NAME}.manpages" + done +} + +generate_sample_manifest(){ + if [[ -d "$INPUT_SAMPLES_DIR" ]]; then + generated_manpages=( $(_get_files_in_dir_tree $INPUT_SAMPLES_DIR) ) + + rm -f sample_manifest + for sample in ${samples[@]} + do + echo "$sample" >> "${DEBIAN_DIR}/${PACKAGE_NAME}.examples" + done + else + echo "Provide a 'samples' directory in INPUT_DIR to package samples" + fi +} + +execute "$@" diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/config_template_generator.py b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/config_template_generator.py new file mode 100644 index 00000000000..6b8b21c9e12 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/config_template_generator.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +# Parses debian_config.json and generates appropriate templates +# Where optional defaults exist, they are defined in the template_dict +# of the appropriate generation function + +import os +import sys +import json +import datetime + +FILE_CHANGELOG = 'changelog' +FILE_CONTROL = 'control' +FILE_COPYRIGHT = 'copyright' +FILE_SYMLINK_FORMAT = '{package_name}.links' +FILE_RULES = 'rules' + +PACKAGE_ROOT_FORMAT = "usr/share/{package_name}" +CHANGELOG_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S %z" + +# UTC Timezone for Changelog date +class UTC(datetime.tzinfo): + def utcoffset(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return datetime.timedelta(0) + +# Generation Functions +def generate_and_write_all(config_data, template_dir, output_dir, package_name=None, package_version=None): + try: + changelog_contents = generate_changelog( + config_data, + template_dir, + package_name=package_name, + package_version=package_version) + + control_contents = generate_control(config_data, template_dir, package_name=package_name) + copyright_contents = generate_copyright(config_data, template_dir) + symlink_contents = generate_symlinks(config_data, package_name=package_name) + rules_contents = generate_rules(config_data, template_dir) + except Exception as exc: + print exc + help_and_exit("Error: Generation Failed, check your config file.") + + write_file(changelog_contents, output_dir, FILE_CHANGELOG) + write_file(control_contents, output_dir, FILE_CONTROL) + write_file(copyright_contents, output_dir, FILE_COPYRIGHT) + write_file(rules_contents, output_dir, FILE_RULES) + + # Symlink File is optional + if symlink_contents: + symlink_filename = get_symlink_filename(config_data, package_name=package_name) + write_file(symlink_contents, output_dir, symlink_filename) + + return + +def generate_rules(config_data, template_dir): + template = get_template(template_dir, FILE_RULES) + + ignored_dependency_packages = config_data.get("debian_ignored_dependencies", None) + override_text = "" + + if ignored_dependency_packages != None: + override_text = "override_dh_shlibdeps:\n\tdh_shlibdeps --dpkg-shlibdeps-params=\"" + + for package in ignored_dependency_packages: + override_text += "-x{0} ".format(package) + + override_text += "\"" + + return template.format(overrides=override_text) + +def generate_changelog(config_data, template_dir, package_version=None, package_name=None): + template = get_template(template_dir, FILE_CHANGELOG) + + release_data = config_data["release"] + + # Allow for Version Override + config_package_version = release_data["package_version"] + package_version = package_version or config_package_version + + template_dict = dict(\ + PACKAGE_VERSION=package_version, + PACKAGE_REVISION=release_data["package_revision"], + CHANGELOG_MESSAGE=release_data["changelog_message"], + URGENCY=release_data.get("urgency", "low"), + + PACKAGE_NAME=package_name or config_data["package_name"], + MAINTAINER_NAME=config_data["maintainer_name"], + MAINTAINER_EMAIL=config_data["maintainer_email"], + DATE=datetime.datetime.now(UTC()).strftime(CHANGELOG_DATE_FORMAT) + ) + + contents = template.format(**template_dict) + + return contents + +def generate_control(config_data, template_dir, package_name=None): + template = get_template(template_dir, FILE_CONTROL) + + dependency_data = config_data.get("debian_dependencies", None) + dependency_str = get_dependendent_packages_string(dependency_data) + + conflict_data = config_data.get("package_conflicts", []) + conflict_str = ', '.join(conflict_data) + + # Default to empty dict, so we don't explode on nested optional values + control_data = config_data.get("control", dict()) + + template_dict = dict(\ + SHORT_DESCRIPTION=config_data["short_description"], + LONG_DESCRIPTION=config_data["long_description"], + HOMEPAGE=config_data.get("homepage", ""), + + SECTION=control_data.get("section", "misc"), + PRIORITY=control_data.get("priority", "low"), + ARCH=control_data.get("architecture", "all"), + + DEPENDENT_PACKAGES=dependency_str, + CONFLICT_PACKAGES=conflict_str, + + PACKAGE_NAME=package_name or config_data["package_name"], + MAINTAINER_NAME=config_data["maintainer_name"], + MAINTAINER_EMAIL=config_data["maintainer_email"] + ) + + contents = template.format(**template_dict) + + return contents + +def generate_copyright(config_data, template_dir): + template = get_template(template_dir, FILE_COPYRIGHT) + + license_data = config_data["license"] + + template_dict = dict(\ + COPYRIGHT_TEXT=config_data["copyright"], + LICENSE_NAME=license_data["type"], + LICENSE_TEXT=license_data["full_text"] + ) + + contents = template.format(**template_dict) + + return contents + +def generate_symlinks(config_data, package_name=None): + symlink_entries = [] + package_root_path = get_package_root(config_data, package_name=package_name) + + symlink_data = config_data.get("symlinks", dict()) + + for package_rel_path, symlink_path in symlink_data.iteritems(): + + package_abs_path = os.path.join(package_root_path, package_rel_path) + + symlink_entries.append( '%s %s' % (package_abs_path, symlink_path) ) + + return '\n'.join(symlink_entries) + +# Helper Functions +def get_package_root(config_data, package_name=None): + config_install_root = config_data.get("install_root", None) + package_name = package_name or config_data["package_name"] + + return config_install_root or PACKAGE_ROOT_FORMAT.format(package_name=package_name) + +def get_symlink_filename(config_data, package_name=None): + package_name = package_name or config_data["package_name"] + return FILE_SYMLINK_FORMAT.format(package_name=package_name) + +def get_dependendent_packages_string(debian_dependency_data): + if debian_dependency_data is None: + return "" + + dependencies = [] + + for debian_package_name in debian_dependency_data: + dep_str = debian_package_name + + if debian_dependency_data[debian_package_name].get("package_version", None): + debian_package_version = debian_dependency_data[debian_package_name].get("package_version") + + dep_str += " (>= %s)" % debian_package_version + + dependencies.append(dep_str) + + # Leading Comma is important here + return ', ' + ', '.join(dependencies) + + +def load_json(json_path): + json_data = None + with open(json_path, 'r') as json_file: + json_data = json.load(json_file) + + return json_data + +def get_template(template_dir, name): + path = os.path.join(template_dir, name) + template_contents = None + + with open(path, 'r') as template_file: + template_contents = template_file.read() + + return template_contents + +def write_file(contents, output_dir, name): + path = os.path.join(output_dir, name) + + with open(path, 'w') as out_file: + out_file.write(contents) + + return + +# Tool Functions +def help_and_exit(msg): + print msg + sys.exit(1) + +def print_usage(): + print "Usage: config_template_generator.py [config file path] [template directory path] [output directory] (package name) (package version)" + +def parse_and_validate_args(): + if len(sys.argv) < 4: + print_usage() + help_and_exit("Error: Invalid Arguments") + + config_path = sys.argv[1] + template_dir = sys.argv[2] + output_dir = sys.argv[3] + name_override = None + version_override = None + + if len(sys.argv) >= 5: + name_override = sys.argv[4] + + if len(sys.argv) >= 6: + version_override = sys.argv[5] + + if not os.path.isfile(config_path): + help_and_exit("Error: Invalid config file path") + + if not os.path.isdir(template_dir): + help_and_exit("Error: Invalid template directory path") + + if not os.path.isdir(output_dir): + help_and_exit("Error: Invalid output directory path") + + return (config_path, template_dir, output_dir, name_override, version_override) + +def execute(): + config_path, template_dir, output_dir, name_override, version_override = parse_and_validate_args() + + config_data = load_json(config_path) + + generate_and_write_all(config_data, template_dir, output_dir, package_name = name_override, package_version=version_override) + +if __name__ == "__main__": + execute() diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/debian_build_lib.sh b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/debian_build_lib.sh new file mode 100644 index 00000000000..18d388864fd --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/debian_build_lib.sh @@ -0,0 +1,171 @@ +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +# This file is not intended to be executed directly +# Import these functions using source +# +# Relies on these environment variables: +# PACKAGE_SOURCE_DIR :: Package Source Staging Directory +# INSTALL_ROOT :: Absolute path of package installation root + +# write_debian_install_file +# Summary: Writes the contents of the "install_placement" array to the debian/install +# This array is populated by calls to the "add_system_file_placement" function +# Usage: write_debian_install_file +write_debian_install_file(){ + # Remove any existing install file, we need to overwrite it + rm -f ${PACKAGE_SOURCE_DIR}/debian/install + + for i in "${install_placement[@]}" + do + echo "${i}" >> "${PACKAGE_SOURCE_DIR}/debian/install" + done +} + +# add_system_file_placement +# Summary: Registers a file placement on the filesystem from the package by populating the "install_placement" array +# Usage: add_system_file_placement {local path of file in package} {absolute path of directory to place file in} +add_system_file_placement(){ + #Initialize placement_index variable + if [[ -z "$placement_index" ]]; then + placement_index=0 + fi + + install_placement[${placement_index}]="${1} ${2}" + placement_index=$((${placement_index}+1)) +} + +# add_system_dir_placement +# Summary: Registers a directory placement on the post-installation package from an in-package path +add_system_dir_placement(){ + + in_package_dir=$1 + abs_installation_dir=$2 + + dir_files=( $(_get_files_in_dir_tree $PACKAGE_SOURCE_DIR/$in_package_dir) ) + + # If in_package_dir isn't empty include a slash + if [ ! -z "$in_package_dir" ]; then + in_package_dir="${in_package_dir}/" + fi + + for rel_filepath in ${dir_files[@]} + do + local parent_path=$(dirname $rel_filepath) + + # If there is no parent, parent_path = "." + if [[ "$parent_path" == "." ]]; then + add_system_file_placement "${in_package_dir}${rel_filepath}" "${abs_installation_dir}" + else + add_system_file_placement "${in_package_dir}${rel_filepath}" "${abs_installation_dir}/${parent_path}" + fi + + done +} + +# add_file_to_install +# Summary: Adds a file from the local filesystem to the package and installs it rooted at INSTALL_ROOT +# Usage: add_install_file {relative path to local file} {relative path to INSTALL_ROOT to place file} +add_file_to_install(){ + copy_from_file=$1 + rel_install_path=$2 + + local filename=$(basename $copy_from_file) + local parent_dir=$(dirname $copy_from_file) + + # Create Relative Copy From Path + rel_copy_from_file=${copy_from_file#$parent_dir/} + + # Delete any existing file and ensure path exists + rm -f ./${PACKAGE_SOURCE_DIR}/${rel_install_path}/${filename} + mkdir -p ./${PACKAGE_SOURCE_DIR}/${rel_install_path} + + dir_files=( "$rel_copy_from_file" ) + + _copy_files_to_package $parent_dir $rel_install_path "${dir_files[@]}" + + add_system_file_placement "${rel_install_path}/${filename}" "${INSTALL_ROOT}/$rel_install_path" +} + +# add_dir_to_install +# Summary: Adds contents of a directory on the local filesystem to the package and installs them rooted at INSTALL_ROOT +# Note: Does not install the directory passed, only its contents +# Usage: add_dir_to_install {relative path of directory to copy} {relative path to INSTALL_ROOT to place directory tree} +add_dir_to_install(){ + + copy_from_dir=$1 + rel_install_path=$2 + + # Delete and Create any existing directory + mkdir -p ${PACKAGE_SOURCE_DIR}/${rel_install_path} + + dir_files=( $(_get_files_in_dir_tree $copy_from_dir) ) + + _copy_files_to_package "$copy_from_dir" "$rel_install_path" "${dir_files[@]}" + + for file in "${dir_files[@]}" + do + file_rel_dir="$(dirname $file)" + add_system_file_placement "${rel_install_path}/${file}" "${INSTALL_ROOT}/$rel_install_path/${file_rel_dir}" + done +} + +# Usage: _copy_files_to_package {local files root directory} {relative directory in package to copy to} "${filepath_array[@]}" +# Note: The specific syntax on the parameter shows how to pass an array +_copy_files_to_package(){ + local_root_dir=$1 + package_dest_dir=$2 + + # Consume the remaining input as an array + shift; shift; + rel_filepath_list=( $@ ) + + for rel_filepath in ${rel_filepath_list[@]} + do + local parent_dir=$(dirname $rel_filepath) + local filename=$(basename $rel_filepath) + + mkdir -p ${PACKAGE_SOURCE_DIR}/${package_dest_dir}/${parent_dir} + + # Ignore $parent_dir if it there isn't one + if [[ "$parent_dir" == "." ]]; then + cp "${local_root_dir}/${rel_filepath}" "${PACKAGE_SOURCE_DIR}/${package_dest_dir}" + else + cp "${local_root_dir}/${rel_filepath}" "${PACKAGE_SOURCE_DIR}/${package_dest_dir}/${parent_dir}" + fi + + done +} + +# Usage: _get_files_in_dir_tree {path of directory} +_get_files_in_dir_tree(){ + + root_dir=$1 + + # Use Globstar expansion to enumerate all directories and files in the tree + shopt -s globstar + shopt -s dotglob + dir_tree_list=( "${root_dir}/"** ) + + # Build a new array with only the Files contained in $dir_tree_list + local index=0 + for file_path in "${dir_tree_list[@]}" + do + if [ -f $file_path ]; then + dir_tree_file_list[${index}]=$file_path + index=$(($index+1)) + fi + done + + # Remove $root_dir prefix from each path in dir_tree_file_list + # This is confusing syntax, so here's a reference link (Substring Removal) + # http://wiki.bash-hackers.org/syntax/pe + dir_tree_file_list=( "${dir_tree_file_list[@]#${root_dir}/}" ) + + # Echo is the return mechanism + echo "${dir_tree_file_list[@]}" +} + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/extract_json_value.py b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/extract_json_value.py new file mode 100644 index 00000000000..74f26a6ffdd --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/extract_json_value.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +# Extract Json Value +# +# Very simple tool to ease extracting json values from the cmd line. +import os +import sys +import json + +def print_usage(): + print """ + Usage: extract_json_value.py [json file path] [key of value to extract] + For nested keys, use . separator + """ + +def help_and_exit(msg=None): + print msg + print_usage() + sys.exit(1) + +def parse_and_validate_args(): + + if len(sys.argv) < 3: + help_and_exit(msg="Error: Invalid Args") + + json_path = sys.argv[1] + json_key = sys.argv[2] + + if not os.path.isfile(json_path): + help_and_exit("Error: Invalid json file path") + + return json_path, json_key + +def extract_key(json_path, json_key): + json_data = None + + with open(json_path, 'r') as json_file: + json_data = json.load(json_file) + + nested_keys = json_key.split('.') + json_context = json_data + + for key in nested_keys: + json_context = json_context.get(key, None) + + if json_context is None: + help_and_exit("Error: Invalid json key") + + return str(json_context) + +def execute(): + json_path, json_key = parse_and_validate_args() + + value = extract_key(json_path, json_key) + + return value + +if __name__ == "__main__": + print execute() + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/manpage_generator.py b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/manpage_generator.py new file mode 100644 index 00000000000..c62e6807901 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/scripts/manpage_generator.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +# manpage_generator +# Converts top level docs.json format command info to +# nroff manpage format. Done in python for easy json parsing. +# +# Usage: argv[1] = path to docs.json; argv[2] = output path + +import sys +import os +import json +import datetime + +SECTION_SEPARATOR = "\n.P \n" +MANPAGE_EXTENSION = ".1" + +# For now this is a magic number +# See https://www.debian.org/doc/manuals/maint-guide/dother.en.html#manpage +SECTION_NUMBER = 1 + +def generate_man_pages(doc_path, output_dir): + + with open(doc_path) as doc_file: + doc_json = None + try: + doc_json = json.load(doc_file) + except: + raise Exception("Failed to load json file. Check formatting.") + + tools = doc_json.get("tools", None) + + if tools is None: + raise Exception("No tool sections in doc.json") + + for tool_name in tools: + tool_data = tools[tool_name] + + man_page_content = generate_man_page(tool_name, tool_data) + man_page_path = get_output_path(tool_name, output_dir) + + write_man_page(man_page_path, man_page_content) + +def get_output_path(toolname, output_dir): + out_filename = toolname + MANPAGE_EXTENSION + + return os.path.join(output_dir, out_filename) + +def write_man_page(path, content): + with open(path, 'w') as man_file: + man_file.write(content) + + #Build Fails without a final newline + man_file.write('\n') + +def generate_man_page(tool_name, tool_data): + + sections = [ + generate_header_section(tool_name, tool_data), + generate_name_section(tool_name, tool_data), + generate_synopsis_section(tool_name, tool_data), + generate_description_section(tool_name, tool_data), + generate_options_section(tool_name, tool_data), + generate_author_section(tool_name, tool_data), + generate_copyright_section(tool_name, tool_data) + ] + + return SECTION_SEPARATOR.join(sections) + +def generate_header_section(tool_name, tool_data):# + roff_text_builder = [] + + header_format = ".TH {program_name} {section_number} {center_footer} {left_footer} {center_header}" + + today = datetime.date.today() + today_string = today.strftime("%B %d, %Y") + + format_args = { + "program_name" : tool_name, + "section_number" : SECTION_NUMBER, + "center_footer" : "", # Omitted + "left_footer" : "", # Omitted + "center_header" : "" # Omitted + } + + roff_text_builder.append(header_format.format(**format_args)) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_name_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH NAME") + + tool_short_description = tool_data.get("short_description", "") + name_format = ".B {program_name} - {short_description}" + + name_format_args = { + "program_name": tool_name, + "short_description" : tool_short_description + } + + roff_text_builder.append(name_format.format(**name_format_args)) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_synopsis_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH SYNOPSIS") + + synopsis_format = '.B {program_name} {command_name} \n.RI {options} " "\n.I "{argument_list_name}"' + + tool_commands = tool_data.get("commands", []) + for command_name in tool_commands: + command_data = tool_commands[command_name] + + # Default options to empty list so the loop doesn't blow up + options = command_data.get("options", []) + argument_list = command_data.get("argumentlist", None) + + # Construct Option Strings + option_string_list = [] + argument_list_name = "" + + for option_name in options: + option_data = options[option_name] + + specifier_short = option_data.get("short", None) + specifier_long = option_data.get("long", None) + parameter = option_data.get("parameter", None) + + option_string = _option_string_helper(specifier_short, specifier_long, parameter) + + option_string_list.append(option_string) + + # Populate Argument List Name + if argument_list: + argument_list_name = argument_list.get("name", "") + + cmd_format_args = { + 'program_name' : tool_name, + 'command_name' : command_name, + 'options' : '" "'.join(option_string_list), + 'argument_list_name' : argument_list_name + } + + cmd_string = synopsis_format.format(**cmd_format_args) + + roff_text_builder.append(cmd_string) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_description_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH DESCRIPTION") + + # Tool Description + long_description = tool_data.get("long_description", "") + roff_text_builder.append(".PP {0}".format(long_description)) + + # Command Descriptions + cmd_description_format = ".B {program_name} {command_name}\n{command_description}" + + tool_commands = tool_data.get("commands", []) + for command_name in tool_commands: + command_data = tool_commands[command_name] + + command_description = command_data.get("description", "") + + format_args = { + "program_name" : tool_name, + "command_name" : command_name, + "command_description" : command_description + } + + cmd_string = cmd_description_format.format(**format_args) + + roff_text_builder.append(cmd_string) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_options_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH OPTIONS") + + options_format = '.TP\n.B {option_specifiers}\n{option_description}' + + tool_commands = tool_data.get("commands", []) + for command_name in tool_commands: + command_data = tool_commands[command_name] + + # Default to empty list so the loop doesn't blow up + options = command_data.get("options", []) + + for option_name in options: + option_data = options[option_name] + + specifier_short = option_data.get("short", None) + specifier_long = option_data.get("long", None) + parameter = option_data.get("parameter", None) + description = option_data.get("description", "") + + option_specifiers_string = _option_string_helper(specifier_short, + specifier_long, + parameter, + include_brackets = False, + delimiter=' ", " ') + + format_args = { + "option_specifiers": option_specifiers_string, + "option_description" : description + } + + roff_text_builder.append(options_format.format(**format_args)) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_author_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH AUTHOR") + + author_format = '.B "{author_name}" " " \n.RI ( "{author_email}" )' + + author_name = tool_data.get("author", "") + author_email = tool_data.get("author_email", "") + + format_args = { + "author_name" : author_name, + "author_email" : author_email + } + + roff_text_builder.append(author_format.format(**format_args)) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def generate_copyright_section(tool_name, tool_data):# + roff_text_builder = [] + roff_text_builder.append(".SH COPYRIGHT") + + copyright_data = tool_data.get("copyright") + + roff_text_builder.append('.B "{0}"'.format(copyright_data)) + + return SECTION_SEPARATOR.join(roff_text_builder) + +def _option_string_helper(specifier_short, specifier_long, parameter, include_brackets = True, delimiter = " | "): + option_string = "" + + if include_brackets: + option_string = " [ " + + if specifier_short: + option_string += ' "{0}" '.format(specifier_short) + + if specifier_short and specifier_long: + option_string += delimiter + + if specifier_long: + option_string += ' "{0}" '.format(specifier_long) + + if parameter: + option_string += ' " " ' + option_string += ' "{0}" '.format(parameter) + + if include_brackets: + option_string += " ] " + + return option_string + + +def print_usage(): + print "Usage: argv[1] = path to docs.json; argv[2] = output path" + print "Example: manpage_generator.py ../docs.json ./dotnet-1.0/debian" + +def parse_args(): + doc_path = sys.argv[1] + output_dir = sys.argv[2] + + return (doc_path, output_dir) + +def validate_args(doc_path, output_dir): + if not os.path.isfile(doc_path): + raise Exception("Docs.json path is not valid.") + + if not os.path.isdir(output_dir): + raise Exception("Output Directory Path is not valid.") + +def execute_command_line(): + try: + doc_path, output_dir = parse_args() + + validate_args(doc_path, output_dir) + + generate_man_pages(doc_path, output_dir) + + except Exception as exc: + print "Error: ", exc + print_usage() + +if __name__ == "__main__": + execute_command_line() diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/build_setup.sh b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/build_setup.sh new file mode 100644 index 00000000000..12c223aacbc --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/build_setup.sh @@ -0,0 +1,21 @@ +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +install_dependencies(){ + # Add LLdb 3.6 package source + echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | tee /etc/apt/sources.list.d/llvm.list + wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | apt-key add - + + #Install Deps + apt-get update + apt-get install -y debhelper build-essential devscripts git liblttng-ust-dev lldb-3.6-dev +} + +setup(){ + install_dependencies +} + +setup \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/test_setup.sh b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/test_setup.sh new file mode 100644 index 00000000000..a7cdd95c2b3 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/setup/test_setup.sh @@ -0,0 +1,28 @@ +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +install_dependencies(){ + # Add LLdb 3.6 package source + echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | tee /etc/apt/sources.list.d/llvm.list + wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | apt-key add - + + #Install Deps + apt-get update + apt-get install -y debhelper build-essential devscripts git liblttng-ust-dev lldb-3.6-dev +} + +install_bats(){ + git clone https://github.com/sstephenson/bats.git + cd bats + ./install.sh /usr/local +} + +setup(){ + install_dependencies + install_bats +} + +setup \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/changelog b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/changelog new file mode 100644 index 00000000000..8eccaab397c --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/changelog @@ -0,0 +1,5 @@ +{PACKAGE_NAME} ({PACKAGE_VERSION}-{PACKAGE_REVISION}) unstable; urgency={URGENCY} + + * {CHANGELOG_MESSAGE} + + -- {MAINTAINER_NAME} <{MAINTAINER_EMAIL}> {DATE} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/control b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/control new file mode 100644 index 00000000000..114d8493042 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/control @@ -0,0 +1,20 @@ +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +Source: {PACKAGE_NAME} +Maintainer: {MAINTAINER_NAME} <{MAINTAINER_EMAIL}> +Section: {SECTION} +Priority: {PRIORITY} +Standards-Version: 3.9.2 +Build-Depends: debhelper (>=9) +Homepage: {HOMEPAGE} + +Package: {PACKAGE_NAME} +Architecture: {ARCH} +Depends: {DEPENDENT_PACKAGES} +Conflicts: {CONFLICT_PACKAGES} +Description: {SHORT_DESCRIPTION} + {LONG_DESCRIPTION} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/copyright b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/copyright new file mode 100644 index 00000000000..72febbba8af --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/copyright @@ -0,0 +1,10 @@ +Comment: Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. + +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: * +Copyright: {COPYRIGHT_TEXT} +License: {LICENSE_NAME} + +License: {LICENSE_NAME} + {LICENSE_TEXT} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/rules b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/rules new file mode 100644 index 00000000000..b5cbfb27e20 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/deb-package-tool/templates/debian/rules @@ -0,0 +1,12 @@ +#!/usr/bin/make -f +# +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. +# + +{overrides} + +%: + dh $@ + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.multirid.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.multirid.targets new file mode 100644 index 00000000000..7ebe747f435 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.multirid.targets @@ -0,0 +1,31 @@ +<Project> + <Target Name="_ComputeInstallerItems"> + <ItemGroup> + <_RuntimeIdentifiers Include="$(InstallerRuntimeIdentifiers)" /> + <!-- Make normalization explicit: Trim; Deduplicate by keeping first occurrence, case insensitive --> + <_RuntimeIdentifiers Include="@(_RuntimeIdentifiers->Trim()->Distinct())" /> + <_InnerBuildProjects Include="$(MSBuildProjectFile)"> + <AdditionalProperties>InstallerRuntimeIdentifier=%(_RuntimeIdentifiers.Identity)</AdditionalProperties> + </_InnerBuildProjects> + </ItemGroup> + </Target> + + <Target Name="GenerateInstallers" + Condition="'$(SkipInstallerBuild)' != 'true'" + DependsOnTargets="_ComputeInstallerItems" + Returns="@(InnerOutput)"> + <Error Condition="'$(InstallerRuntimeIdentifiers)' == ''" + Text="At least one RID must be specified via InstallerRuntimeIdentifiers to build installers." /> + <!-- If this logic is changed, also update Clean --> + <MSBuild Projects="@(_InnerBuildProjects)" + Condition="'@(_InnerBuildProjects)' != '' " + Targets="GenerateInstallers" + BuildInParallel="$(BuildInParallel)"> + <Output ItemName="InnerOutput" TaskParameter="TargetOutputs" /> + </MSBuild> + </Target> + + <PropertyGroup> + <BuildDependsOn>$(BuildDependsOn);_GetSkipInstallerBuildProps;GenerateInstallers</BuildDependsOn> + </PropertyGroup> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props new file mode 100644 index 00000000000..5fc6934544b --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.props @@ -0,0 +1,15 @@ +<Project> + <PropertyGroup> + <VersionInstallerName>true</VersionInstallerName> + <IncludeVersionInMacOSComponentName>true</IncludeVersionInMacOSComponentName> + </PropertyGroup> + + <UsingTask TaskName="BuildFPMToolPreReqs" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)"/> + <UsingTask TaskName="CreateLightCommandPackageDrop" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <UsingTask TaskName="ExecWithRetries" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <UsingTask TaskName="GenerateCurrentVersion" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <UsingTask TaskName="GenerateGuidFromName" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <UsingTask TaskName="GenerateJsonObjectString" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)"/> + <UsingTask TaskName="GenerateMsiVersion" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> + <UsingTask TaskName="StabilizeWixFileId" AssemblyFile="$(MicrosoftDotNetBuildTasksInstallersTaskAssembly)" /> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.singlerid.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.singlerid.targets new file mode 100644 index 00000000000..28d2ca9574d --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.singlerid.targets @@ -0,0 +1,481 @@ +<Project> + <PropertyGroup> + <_InstallerTargetsImported>true</_InstallerTargetsImported> + </PropertyGroup> + <Target Name="GetInstallerGenerationFlags" DependsOnTargets="_GetTargetOSArchInfo"> + <!-- Filter the installer generation/build flags for the current build machine. --> + <PropertyGroup> + <!-- + Save the project's GenerateInstallers setting. It's possible the current arch of the current OS + doesn't support MSI generation, but we still want to create an MSI across architectures. For + example, we want to make an MSI that installs the arm apphost pack into the x64 SDK location + on an x64 machine. + --> + <GenerateCrossArchMsi Condition="'$(_osSupportsWixBasedInstallers)' == 'true'">true</GenerateCrossArchMsi> + + <GenerateMSI Condition="'$(_osArchSupportsWixBasedInstallers)' == 'true'">true</GenerateMSI> + + <GeneratePkg Condition="$([MSBuild]::IsOSPlatform('osx')) and '$(TargetRuntimeOS)' == 'osx'">true</GeneratePkg> + </PropertyGroup> + + <!-- + Apply the global Build*Package properties. These use distro-specific logic to determine if the + current distro should build RPM packages or Debian packages. + + This logic is external because the legacy infra needs the values. + --> + <PropertyGroup> + <GenerateDeb>$(GenerateInstallers)</GenerateDeb> + <GenerateRpm>$(GenerateInstallers)</GenerateRpm> + <GenerateDeb Condition="'$(GenerateDeb)' == 'true' and '$(BuildDebPackage)' != 'true'">false</GenerateDeb> + <GenerateRpm Condition="'$(GenerateRpm)' == 'true' and '$(BuildRpmPackage)' != 'true'">false</GenerateRpm> + </PropertyGroup> + </Target> + + <!-- + Shared targets to build installers and distro packages. + --> + <Import Project="$(MSBuildThisFileDirectory)wix/wix.targets" /> + + <Target Name="GenerateInstallers" DependsOnTargets="GetInstallerGenerationFlags" Condition="'$(SkipInstallerBuild)' != 'true' and '$(GenerateInstallers)' == 'true'"> + <ItemGroup> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GenerateDeb" + Properties="GenerateDeb=true" + Condition="'$(GenerateDeb)' == 'true'" /> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GenerateRpm" + Properties="GenerateRpm=true" + Condition="'$(GenerateRpm)' == 'true'" /> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GenerateMsi" + Properties="GenerateMSI=true" + Condition="'$(GenerateMSI)' == 'true'" /> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GenerateCrossArchMsi" + Properties="GenerateCrossArchMsi=true" + Condition="'$(GenerateCrossArchMsi)' == 'true'" /> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GeneratePkg" + Properties="GeneratePkg=true" + Condition="'$(GeneratePkg)' == 'true'" /> + <_InstallerBuildProject + Include="$(MSBuildProjectFile)" + Targets="GenerateVSInsertionNupkg" + Properties="GenerateMSI=$(GenerateMSI);GenerateCrossArchMsi=$(GenerateCrossArchMsi)" + Condition="'$(GenerateVSInsertionPackages)' == 'true'" /> + </ItemGroup> + <MSBuild Projects="@(_InstallerBuildProject)" + Targets="%(_InstallerBuildProject.Targets)" + BuildInParallel="$(BuildInParallel)" /> + </Target> + + <Target Name="GenerateDeb" DependsOnTargets="FindDebuild;CreateDeb" /> + <Target Name="GenerateRpm" DependsOnTargets="FindFPM;CreateRpm" /> + <Target Name="GenerateMsi" DependsOnTargets="CreateWixInstaller" /> + <Target Name="GenerateCrossArchMsi" DependsOnTargets="CreateCrossArchWixInstaller" /> + <Target Name="GeneratePkg" DependsOnTargets="CreatePkg" /> + + <!-- + Set up properties for installer generation. Project-type-specific properties are set up in + targets that use BeforeTargets="_GetInstallerProperties". + --> + <Target Name="_GetInstallerProperties" + DependsOnTargets="_GetTargetOSArchInfo; + _GetProductBrandName"> + <Error + Text="InstallerName '$(InstallerName)' is empty or starts with a '-': expected a value like 'dotnet-runtime'." + Condition="'$(InstallerName)' == '' or $(InstallerName.StartsWith('-'))" /> + + <PropertyGroup> + <VersionedInstallerName Condition="'$(VersionInstallerName)' == 'true'">$(InstallerName)-$(MajorVersion).$(MinorVersion)</VersionedInstallerName> + <VersionedInstallerName Condition="'$(VersionInstallerName)' != 'true'">$(InstallerName)</VersionedInstallerName> + <VersionedInstallerName>$(VersionedInstallerName.ToLowerInvariant())</VersionedInstallerName> + <InstallerPackageRelease>1</InstallerPackageRelease> + <InstallerPackageVersion>$(VersionPrefix)</InstallerPackageVersion> + </PropertyGroup> + + <!-- Distinguish the cross-arch installer filename. --> + <PropertyGroup Condition="'$(CrossArchContentsArch)' != ''"> + <CrossArchContentsBuildPart>_$(CrossArchContentsArch)</CrossArchContentsBuildPart> + </PropertyGroup> + + <PropertyGroup> + <InstallerExtension Condition="'$(GenerateMsi)' == 'true' or '$(GenerateCrossArchMsi)' == 'true'">.msi</InstallerExtension> + <InstallerExtension Condition="'$(GeneratePkg)' == 'true'">.pkg</InstallerExtension> + <InstallerExtension Condition="'$(GenerateDeb)' == 'true'">.deb</InstallerExtension> + <InstallerExtension Condition="'$(GenerateRpm)' == 'true'">.rpm</InstallerExtension> + <CombinedInstallerExtension Condition="'$(TargetRuntimeOS)' == 'win'">.exe</CombinedInstallerExtension> + <CombinedInstallerExtension Condition="'$(TargetRuntimeOS)' != 'win'">$(InstallerExtension)</CombinedInstallerExtension> + </PropertyGroup> + + <PropertyGroup Condition="'$(GenerateDeb)' == 'true'"> + <InstallerPackageVersion Condition="'$(IncludePreReleaseLabelInPackageVersion)' == 'true'">$(VersionPrefix)~$(VersionSuffix)</InstallerPackageVersion> + </PropertyGroup> + + <PropertyGroup Condition="'$(GenerateRpm)' == 'true'"> + <InstallerPackageRelease Condition="'$(IncludePreReleaseLabelInPackageVersion)' == 'true'">0.1.$(VersionSuffix)</InstallerPackageRelease> + <InstallerPackageRelease>$([System.String]::Copy('$(InstallerPackageRelease)').Replace('-', '_'))</InstallerPackageRelease> + </PropertyGroup> + + <PropertyGroup> + <_InstallerIntermediatesDir>$(IntermediateOutputPath)$(InstallerName)/$(InstallerPackageVersion)/</_InstallerIntermediatesDir> + + <InstallerBuildPart>$(Version)-$(TargetRuntimeOS)-$(InstallerTargetArchitecture)</InstallerBuildPart> + </PropertyGroup> + <PropertyGroup Condition="'$(GenerateDeb)' == 'true' or '$(GenerateRpm)' == 'true'"> + <InstallerBuildPart>$(ProductVersion)-$(InstallerTargetArchitecture)</InstallerBuildPart> + <InstallerBuildPart Condition="'$(PackageTargetOS)' != ''">$(ProductVersion)-$(PackageTargetOS)-$(InstallerTargetArchitecture)</InstallerBuildPart> + </PropertyGroup> + <PropertyGroup> + <!-- Location to place the installer, in artifacts. --> + <InstallerFileNameWithoutExtension>$(InstallerName)-$(InstallerBuildPart)$(CrossArchContentsBuildPart)</InstallerFileNameWithoutExtension> + <_InstallerFile Condition="'$(_InstallerFile)' == ''">$(PackageOutputPath)$(InstallerFileNameWithoutExtension)$(InstallerExtension)</_InstallerFile> + <ExeBundleInstallerFile>$(PackageOutputPath)$(InstallerFileNameWithoutExtension).exe</ExeBundleInstallerFile> + <ExeBundleInstallerEngineFile>$(PackageOutputPath)$(InstallerFileNameWithoutExtension)-engine.exe</ExeBundleInstallerEngineFile> + </PropertyGroup> + </Target> + + <!-- + Check if the deb package build tool prereq is available. + --> + <Target Name="FindDebuild"> + <!-- run Debuild --> + <Exec + Command="/usr/bin/env debuild -h > /dev/null 2>&1" + ContinueOnError="true" + IgnoreExitCode="true" + IgnoreStandardErrorWarningFormat="true"> + <Output TaskParameter="ExitCode" PropertyName="_DebuildSearchExitCode" /> + </Exec> + + <!-- Check if it successfully showed us a help message. --> + <PropertyGroup> + <_DebuildPresent>false</_DebuildPresent> + <_DebuildPresent Condition=" '$(_DebuildSearchExitCode)' == '0' ">true</_DebuildPresent> + </PropertyGroup> + + <Error Condition=" '$(_DebuildPresent)' != 'true' " + Text="Debuild Not found, Debian packages will not be built." /> + </Target> + + <!-- + Check if the RPM package build tool prereq is available. + --> + <Target Name="FindFPM"> + <!-- run FPM --> + <Exec + Command="/usr/bin/env fpm -h > /dev/null 2>&1" + ContinueOnError="true" + IgnoreExitCode="true" + IgnoreStandardErrorWarningFormat="true"> + <Output TaskParameter="ExitCode" PropertyName="_FPMSearchExitCode" /> + </Exec> + + <!-- Check if it successfully showed us a help message. --> + <PropertyGroup> + <_FPMPresent>false</_FPMPresent> + <_FPMPresent Condition=" '$(_FPMSearchExitCode)' == '0' ">true</_FPMPresent> + </PropertyGroup> + + <Error Condition=" '$(_FPMPresent)' != 'true' " + Text="FPM tool Not found, RPM packages will not be built." /> + </Target> + + <!-- + Create Debian package. + --> + <Target Name="CreateDeb" + DependsOnTargets=" + _GetInstallerProperties; + _CreateInstallerLayout; + GetDebInstallerJsonProperties" + Condition="'$(_DebuildPresent)' == 'true'" + Returns="$(_InstallerFile)"> + <PropertyGroup> + <_ConfigJsonFile>$(_LayoutDirectory)debian_config.json</_ConfigJsonFile> + <_DebIntermediatesDir>$(IntermediateOutputPath)out-deb</_DebIntermediatesDir> + + <_DebToolArgs>-i $(_LayoutDirectory)</_DebToolArgs> + <_DebToolArgs>$(_DebToolArgs) -o $(_DebIntermediatesDir)</_DebToolArgs> + <_DebToolArgs>$(_DebToolArgs) -n $(VersionedInstallerName)</_DebToolArgs> + <_DebToolArgs>$(_DebToolArgs) -v $(InstallerPackageVersion)</_DebToolArgs> + </PropertyGroup> + + <!-- Write the configuration JSON. --> + <GenerateJsonObjectString + Properties="@(_CommonLinuxPackageProperty);@(_DebJsonProperty)" + TargetFile="$(_ConfigJsonFile)" /> + + <!-- Run deb tool in the directory of the consumer project. --> + <Exec + Command="/usr/bin/env bash $(MSBuildThisFileDirectory)deb-package-tool/package_tool.sh $(_DebToolArgs)" + IgnoreStandardErrorWarningFormat="true" /> + + <!-- Copy package to output. --> + <ItemGroup> + <_GeneratedDebFiles Include="$(_DebIntermediatesDir)/*.deb" /> + </ItemGroup> + + <Error Text="@(_GeneratedDebFiles->Count()) .deb files generated." Condition="'@(_GeneratedDebFiles->Count())' != 1" /> + + <Copy SourceFiles="@(_GeneratedDebFiles)" + DestinationFiles="$(_InstallerFile)" + OverwriteReadOnlyFiles="True" + SkipUnchangedFiles="False" + UseHardlinksIfPossible="False" /> + + <Message Text="$(MSBuildProjectName) -> $(_InstallerFile)" Importance="high" /> + </Target> + + <Target Name="GetDebInstallerJsonProperties" + DependsOnTargets="_GetCommonJsonProperties"> + <ItemGroup> + <_DebDependenciesJsonEntry Include="@(LinuxPackageDependency->'"%(Identity)" : { "package_version" : "%(Version)"}')" /> + </ItemGroup> + <ItemGroup> + <_DebJsonProperty Include="debian_dependencies" Object="{@(_DebDependenciesJsonEntry, ', ')}" /> + <_DebJsonProperty Include="@(DebJsonProperty)" /> + </ItemGroup> + </Target> + + <!-- + Create RPM package. + --> + <Target Name="CreateRpm" + DependsOnTargets=" + _GetInstallerProperties; + _CreateInstallerLayout; + GetRpmInstallerJsonProperties" + Condition="'$(_FPMPresent)' == 'true'" + Returns="$(_InstallerFile)"> + <PropertyGroup> + <_ConfigJsonFile>$(_LayoutDirectory)rpm_config.json</_ConfigJsonFile> + <_RpmIntermediatesDir>$(_InstallerIntermediatesDir)out-rpm</_RpmIntermediatesDir> + + <!-- Copyright, Changelog --> + <_RpmTemplatesLayoutDir>$(_LayoutDirectory)templates/</_RpmTemplatesLayoutDir> + </PropertyGroup> + + <ItemGroup> + <_RpmTemplateFile Include="$(MSBuildThisFileDirectory)rpm_templates/**/*" /> + </ItemGroup> + + <Copy + SourceFiles="@(_RpmTemplateFile)" + DestinationFiles="@(_RpmTemplateFile->'$(_RpmTemplatesLayoutDir)%(RecursiveDir)%(Filename)%(Extension)')" /> + + <GenerateJsonObjectString + Properties="@(_CommonLinuxPackageProperty);@(_RpmJsonProperty)" + TargetFile="$(_ConfigJsonFile)" /> + + <MakeDir Directories="$(_RpmIntermediatesDir)" /> + + <!-- Call the task to build the pre-reqs (parameters, copyright, changelog) for calling the FPM tool --> + <BuildFPMToolPreReqs + InputDir="$(_LayoutDirectory)" + OutputDir="$(_RpmIntermediatesDir)" + PackageVersion="$(InstallerPackageVersion)" + ConfigJsonFile="$(_ConfigJsonFile)"> + <Output TaskParameter="FPMParameters" PropertyName="FPMCmdParameters" /> + </BuildFPMToolPreReqs> + + <Exec Command="fpm $(FPMCmdParameters)" WorkingDirectory="$(_RpmIntermediatesDir)" /> + + <!-- Copy package to output --> + <ItemGroup> + <GeneratedRpmFiles Include="$(_RpmIntermediatesDir)/*.rpm" /> + </ItemGroup> + + <Error Text="@(GeneratedRpmFiles->Count()) .rpm files generated." Condition="'@(GeneratedRpmFiles->Count())' != 1" /> + + <Copy SourceFiles="@(GeneratedRpmFiles)" + DestinationFiles="$(_InstallerFile)" + OverwriteReadOnlyFiles="True" + SkipUnchangedFiles="False" + UseHardlinksIfPossible="False" /> + + <Message Text="$(MSBuildProjectName) -> $(_InstallerFile)" Importance="high" /> + </Target> + + <Target Name="GetRpmInstallerJsonProperties" + DependsOnTargets="_GetCommonJsonProperties"> + <ItemGroup> + <_FpmDependenciesJsonEntry Include="@(LinuxPackageDependency->'{"package_name" : "%(Identity)", "package_version" : "%(Version)"}')" /> + </ItemGroup> + <ItemGroup> + <_RpmJsonProperty Include="vendor" String=".NET Foundation" /> + <_RpmJsonProperty Include="install_doc" String="/usr/share/doc/$(VersionedInstallerName)/" /> + <_RpmJsonProperty Include="rpm_dependencies" Object="[@(_FpmDependenciesJsonEntry,', ')]" /> + <_RpmJsonProperty Include="@(RpmJsonProperty)" /> + </ItemGroup> + </Target> + + <!-- + Create MSI installer, using WiX tools. + --> + <Target Name="CreateWixInstaller" + DependsOnTargets=" + _GetInstallerProperties; + RunLightLinker" + Returns="$(_OutInstallerFile)"> + <Error + Condition="'$(GenerateExeBundle)' == 'true' and '$(GenerateMSI)' == 'true'" + Text="GenerateExeBundle and GenerateMSI are both set, but only one can be created at a time." /> + + <Message Text="$(MSBuildProjectName) -> $(_OutInstallerFile)" Importance="high" /> + </Target> + + <!-- + Create MSI installers that install the current architecture's assets into the proper location + for a different architecture's SDK to find. + --> + <Target Name="CreateCrossArchWixInstaller" + DependsOnTargets="_GetTargetOSArchInfo" + Returns="@(_CrossArchMsi)"> + <MSBuild + Condition="'@(CrossArchMsiToBuild)' != ''" + Projects="$(MSBuildProjectFullPath)" + Targets="CreateWixInstaller" + Properties=" + InstallerTargetArchitecture=%(CrossArchMsiToBuild.Identity); + CrossArchContentsArch=$(TargetArchitecture); + GenerateMSI=true"> + <Output TaskParameter="TargetOutputs" ItemName="_CrossArchMsi" /> + </MSBuild> + </Target> + + <!-- + Create macOS pkg installer. + --> + <Target Name="CreatePkg" + DependsOnTargets=" + _GetInstallerProperties; + _CreateInstallerLayout" + Returns="@(_CreatedPkg)"> + + <!-- Copy files to layout. --> + <PropertyGroup> + <_OutputPathRoot>$(IntermediateOutputPath)output/</_OutputPathRoot> + </PropertyGroup> + + <MSBuild Projects="$(MSBuildProjectFullPath)" + Targets="PublishToDisk" + Properties="OutputPath=$(_OutputPathRoot)" + RemoveProperties="@(_GlobalPropertiesToRemoveForPublish)" /> + <PropertyGroup> + <_MacOSVersionComponent Condition="'$(IncludeVersionInMacOSComponentName)' == 'true'">.$(ProductVersion)</_MacOSVersionComponent> + <_MacOSComponentName Condition="'$(_MacOSComponentName)' == ''">com.microsoft.dotnet.$(MacOSComponentNamePackType)$(_MacOSVersionComponent).component.osx.x64</_MacOSComponentName> + <_MacOSSharedInstallDir>/usr/local/share/dotnet</_MacOSSharedInstallDir> + + <_pkgArgs></_pkgArgs> + <_pkgArgs>$(_pkgArgs) --root $(_OutputPathRoot)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --identifier $(_MacOSComponentName)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --version $(ProductVersion)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) --install-location $(_MacOSSharedInstallDir)</_pkgArgs> + <_pkgArgs Condition="'$(MacOSScriptsDirectory)' != ''">$(_pkgArgs) --scripts $(MacOSScriptsDirectory)</_pkgArgs> + <_pkgArgs>$(_pkgArgs) $(_InstallerFile)</_pkgArgs> + </PropertyGroup> + + <Exec Command="pkgbuild $(_pkgArgs)" /> + + <Message Text="$(MSBuildProjectName) -> $(_InstallerFile)" Importance="high" /> + <ItemGroup> + <_CreatedPkg Include="$(_InstallerFile)" Description="$(MacOSPackageDescription)" Title="$(ProductBrandName) ($(TargetArchitecture))" /> + </ItemGroup> + </Target> + + <!-- + Create installer layout. Used for RPM and Deb package creation. + --> + <Target Name="_CreateInstallerLayout"> + <PropertyGroup> + <_LayoutDirectory>$(IntermediateOutputPath)installer/layoutDirectory/</_LayoutDirectory> + <_LayoutPackageRoot>$(_LayoutDirectory)package_root</_LayoutPackageRoot> + <_LayoutAbsolute>$(_LayoutDirectory)$</_LayoutAbsolute> + <_LayoutSamples>$(_LayoutDirectory)samples</_LayoutSamples> + <_LayoutDocs>$(_LayoutDirectory)docs</_LayoutDocs> + </PropertyGroup> + + <RemoveDir Condition="Exists('$(_InstallerIntermediatesDir)')" Directories="$(_InstallerIntermediatesDir)" /> + <MakeDir Directories="$(_InstallerIntermediatesDir)" /> + + <!-- Create empty layout. --> + <RemoveDir Condition="Exists('$(_LayoutDirectory)')" Directories="$(_LayoutDirectory)" /> + <MakeDir Directories="$(_LayoutDirectory)" /> + <MakeDir Directories="$(_LayoutAbsolute)" /> + <MakeDir Directories="$(_LayoutPackageRoot)" /> + <MakeDir Directories="$(_LayoutSamples)" /> + <MakeDir Directories="$(_LayoutDocs)" /> + + <MSBuild Projects="$(MSBuildProjectFullPath)" + Targets="PublishToDisk" + Properties="OutputPath=$(_LayoutPackageRoot)" + RemoveProperties="@(_GlobalPropertiesToRemoveForPublish)" /> + + <Copy + SourceFiles="@(Manpage)" + DestinationFiles="@(Manpage->'$(_LayoutDocs)/%(RecursiveDir)%(Filename)%(Extension)')" /> + </Target> + + <!-- + Get common JSON properties. Used in the configuration JSON for both RPM and Debian packages. + --> + <Target Name="_GetCommonJsonProperties"> + <PropertyGroup> + <FullLicenseText>$([System.IO.File]::ReadAllText('$(LicenseFile)').Replace('%0A', '\n').Replace('"', '\"'))</FullLicenseText> + </PropertyGroup> + + <ItemGroup> + <_LinuxPackageReleaseProperty Include="package_version" String="1.0.0.0" /> + <_LinuxPackageReleaseProperty Include="package_revision" String="$(InstallerPackageRelease)" /> + <_LinuxPackageReleaseProperty Include="urgency" String="low" /> + <_LinuxPackageReleaseProperty Include="changelog_message" String="https://github.com/dotnet/core/tree/master/release-notes" /> + + <_LinuxPackageControlProperty Include="priority" String="standard" /> + <_LinuxPackageControlProperty Include="section" String="libs" /> + <_LinuxPackageControlProperty Include="architecture" String="amd64" /> + + <_LinuxPackageLicenseProperty Include="type" String="MIT and ASL 2.0 and BSD" /> + <_LinuxPackageLicenseProperty Include="full_text" String="$(FullLicenseText)" /> + </ItemGroup> + + <GenerateJsonObjectString Properties="@(_LinuxPackageReleaseProperty)"> + <Output TaskParameter="Json" PropertyName="_JsonReleaseObject" /> + </GenerateJsonObjectString> + <GenerateJsonObjectString Properties="@(_LinuxPackageControlProperty)"> + <Output TaskParameter="Json" PropertyName="_JsonControlObject" /> + </GenerateJsonObjectString> + <GenerateJsonObjectString Properties="@(_LinuxPackageLicenseProperty)"> + <Output TaskParameter="Json" PropertyName="_JsonLicenseObject" /> + </GenerateJsonObjectString> + + <PropertyGroup> + <_ShortDescription>$(MSBuildProjectName) $(InstallerPackageVersion)</_ShortDescription> + <_ShortDescription Condition="'$(UseBrandingNameInLinuxPackageDescription)' == 'true'">$(ProductBrandName)</_ShortDescription> + </PropertyGroup> + + <ItemGroup> + <_CommonLinuxPackageProperty Include="package_name" String="$(VersionedInstallerName)" /> + <_CommonLinuxPackageProperty Include="short_description" String="$(_ShortDescription)" /> + <_CommonLinuxPackageProperty Include="maintainer_name" String=".NET Team" /> + <_CommonLinuxPackageProperty Include="maintainer_email" String="dotnetpackages@dotnetfoundation.org" /> + <_CommonLinuxPackageProperty Include="install_root" String="/usr/share/dotnet" /> + <_CommonLinuxPackageProperty Include="long_description" String=".NET is a development platform that you can use to build command-line applications, microservices and modern websites. It is open source, cross-platform and is supported by Microsoft. We hope you enjoy using it! If you do, please consider joining the active community of developers that are contributing to the project on GitHub (https://github.com/dotnet/core). We happily accept issues and PRs." /> + <_CommonLinuxPackageProperty Include="homepage" String="https://github.com/dotnet/core" /> + <_CommonLinuxPackageProperty Include="copyright" String="2017 Microsoft" /> + <_CommonLinuxPackageProperty Include="release" Object="$(_JsonReleaseObject)" /> + <_CommonLinuxPackageProperty Include="control" Object="$(_JsonControlObject)" /> + <_CommonLinuxPackageProperty Include="license" Object="$(_JsonLicenseObject)" /> + </ItemGroup> + </Target> + + <PropertyGroup> + <BuildDependsOn>$(BuildDependsOn);_GetSkipInstallerBuildProps;GenerateInstallers</BuildDependsOn> + </PropertyGroup> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.targets new file mode 100644 index 00000000000..9f0427cf6b0 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/installer.targets @@ -0,0 +1,155 @@ +<Project> + <Target Name="_GetCurrentProjectServicingConfiguration"> + <ItemGroup> + <CurrentProjectServicingConfiguration + Include="@(ProjectServicingConfiguration)" + Condition="'%(Identity)' == '$(MSBuildProjectName)'" /> + </ItemGroup> + </Target> + + <!-- + The Microsoft build's per-package servicing policy conflicts with the source-build restrictions. + Targeting packs, for example, are only built/published when there's a known change to release. + This is in contrast to runtime packs and the shared framework, which are always built and + published. This means it's common in the Microsoft build for downstream repos to depend on two + builds' outputs: the current build's runtime assets, and some old build's targeting pack. + + The Microsoft build can simply download the old targeting pack from NuGet.org. Source-build + can't do this because the bits on NuGet.org are not built locally. Instead, source-build assumes + it's possible to use current sources to build a package with the old version. This target + applies the old build's patch version to make that happen. + + This solution has pitfalls. More info at https://github.com/dotnet/core-setup/issues/8735. The + target supports SkipSetLastReleasedVersionForSourceBuild (unused as of writing) to allow + disabling this workaround if a better way forward is implemented. + --> + <Target Name="SetLastReleasedVersionForSourceBuild" + Condition=" + '$(DotNetBuildFromSource)' == 'true' and + '$(SkipSetLastReleasedVersionForSourceBuild)' != 'true'" + BeforeTargets="GetProductVersions" + DependsOnTargets="_GetCurrentProjectServicingConfiguration"> + <PropertyGroup> + <MostRecentProducedServicingPatchVersion>%(CurrentProjectServicingConfiguration.PatchVersion)</MostRecentProducedServicingPatchVersion> + <PatchVersion Condition="'$(MostRecentProducedServicingPatchVersion)' != ''">$(MostRecentProducedServicingPatchVersion)</PatchVersion> + </PropertyGroup> + </Target> + + <Target Name="_GetSkipInstallerBuildProps" + DependsOnTargets=" + _GetCurrentProjectServicingConfiguration; + SetLastReleasedVersionForSourceBuild"> + <!-- + Skip the build if there is an applicable servicing configuration, and the servicing + configuration indicates this project shouldn't build for this patch version. + --> + <PropertyGroup Condition="'@(CurrentProjectServicingConfiguration)' != ''"> + <SkipInstallerBuild Condition="'%(CurrentProjectServicingConfiguration.PatchVersion)' != '$(PatchVersion)'">true</SkipInstallerBuild> + </PropertyGroup> + + <ItemGroup> + <_TargetInstallerRuntimeIdentifiers Include="$(InstallerRuntimeIdentifiers)" /> + </ItemGroup> + + <PropertyGroup Condition="'$(InstallerRuntimeIdentifiers)' != '' and '$(InstallerRuntimeIdentifier)' != ''"> + <!-- Avoid building a project when none of the possible InstallerRuntimeIdentifiers is the current InstallerRuntimeIdentifier. --> + <_InstallerRidInInstallerRidList Condition="'%(_TargetInstallerRuntimeIdentifiers.Identity)' == '$(InstallerRuntimeIdentifier)'">true</_InstallerRidInInstallerRidList> + <SkipInstallerBuild Condition="'$(_InstallerRidInInstallerRidList)' != 'true'">true</SkipInstallerBuild> + </PropertyGroup> + <PropertyGroup Condition="'$(SkipBuild)' == 'true'"> + <SkipInstallerBuild>true</SkipInstallerBuild> + </PropertyGroup> + </Target> + + <PropertyGroup> + <BuildDependsOn>$(BuildDependsOn);_GetSkipInstallerBuildProps</BuildDependsOn> + <_GlobalPropertiesToRemoveForPublish> + GenerateCrossArchMsi; + GenerateMSI; + GeneratePkg; + GenerateDeb; + GenerateRpm; + IsShipping; + ComponentMsiFile; + InstallerRuntimeIdentifier; + InstallerTargetArchitecture; + CrossArchContentsArch + </_GlobalPropertiesToRemoveForPublish> + </PropertyGroup> + + <ItemGroup> + <_GlobalPropertiesToRemoveForPublish Include="$(_GlobalPropertiesToRemoveForPublish)" /> + </ItemGroup> + + <Target Name="_GetTargetOSArchInfo"> + <Error Condition="'$(InstallerRuntimeIdentifier)' == ''" + Text="An InstallerRuntimeIdentifier must be specified when building installers." /> + <PropertyGroup> + <TargetRuntimeOS>$(InstallerRuntimeIdentifier.Substring(0, $(InstallerRuntimeIdentifier.LastIndexOf('-'))))</TargetRuntimeOS> + <TargetArchitecture Condition="'$(TargetArchitecture)' == ''">$(InstallerRuntimeIdentifier.Substring($(InstallerRuntimeIdentifier.LastIndexOf('-'))).TrimStart('-'))</TargetArchitecture> + <InstallerTargetArchitecture Condition="'$(InstallerTargetArchitecture)' == ''">$(TargetArchitecture)</InstallerTargetArchitecture> + </PropertyGroup> + <ItemGroup> + <CrossArchMsiToBuild Include="@(CrossArchSdkMsiInstallerArch)" Exclude="$(TargetArchitecture)" /> + </ItemGroup> + <PropertyGroup> + <_osSupportsWixBasedInstallers Condition="$([MSBuild]::IsOsPlatform(Windows)) and '$(TargetRuntimeOS)' == 'win'">true</_osSupportsWixBasedInstallers> + + <_osArchSupportsWixBasedInstallers>$(_osSupportsWixBasedInstallers)</_osArchSupportsWixBasedInstallers> + <_osArchSupportsWixBasedInstallers Condition="'$(TargetArchitecture)' == 'arm'">false</_osArchSupportsWixBasedInstallers> + </PropertyGroup> + </Target> + + <Target Name="_GetVersionInfo"> + <PropertyGroup> + <IncludePreReleaseLabelInPackageVersion Condition="'$(DotNetFinalVersionKind)' != 'release'">true</IncludePreReleaseLabelInPackageVersion> + <IncludePreReleaseLabelInPackageVersion Condition="'$(SuppressFinalPackageVersion)' == 'true'">true</IncludePreReleaseLabelInPackageVersion> + <IncludePreReleaseLabelInPackageVersion Condition="'$(IsShipping)' != 'true'">true</IncludePreReleaseLabelInPackageVersion> + </PropertyGroup> + </Target> + + <Target Name="_GetProductBrandName" DependsOnTargets="_GetVersionInfo"> + <PropertyGroup + Condition=" + '$(ReleaseBrandSuffix)' == '' and + '$(PreReleaseVersionLabel)' != '' and + '$(PreReleaseVersionIteration)' != ''"> + <!-- Convert 'preview.7' to 'Preview 7'. + 'preview' will come from the pre-release version iteration and the numeric value + will be the PreReleaseVersionIteration. --> + + <ReleaseBrandSuffix>$(PreReleaseVersionLabel.Substring(0,1).ToUpperInvariant())</ReleaseBrandSuffix> + <ReleaseBrandSuffix>$(ReleaseBrandSuffix)$(PreReleaseVersionLabel.Substring(1))</ReleaseBrandSuffix> + <ReleaseBrandSuffix>$(ReleaseBrandSuffix) $(PreReleaseVersionIteration)</ReleaseBrandSuffix> + </PropertyGroup> + + <Error + Text="When building installers a ProductBrandPrefix is required." + Condition="'$(ProductBrandPrefix)' == ''" /> + <Error + Text="When building installers a PackageBrandNameSuffix is required." + Condition="'$(PackageBrandNameSuffix)' == '' and '$(MSBuildProjectExtension)' != '.bundleproj'" /> + <Error + Text="When building a bundle installer, a BundleNameSuffix must be specified." + Condition="'$(BundleNameSuffix)' == '' and '$(MSBuildProjectExtension)' == '.bundleproj'" /> + + <PropertyGroup> + <ProductBrandSuffix>$(VersionPrefix)</ProductBrandSuffix> + <ProductBrandSuffix Condition="'$(ReleaseBrandSuffix)'!=''">$(VersionPrefix) $(ReleaseBrandSuffix)</ProductBrandSuffix> + <ProductBrandName Condition="'$(MSBuildProjectExtension)' == '.bundleproj'">$(ProductBrandPrefix) $(BundleNameSuffix) - $(ProductBrandSuffix)</ProductBrandName> + <ProductBrandName Condition="'$(ProductBrandName)' == ''">$(ProductBrandPrefix) $(PackageBrandNameSuffix) - $(ProductBrandSuffix)</ProductBrandName> + </PropertyGroup> + </Target> + + <PropertyGroup> + <InstallerRuntimeIdentifiers Condition="'$(InstallerRuntimeIdentifiers)' == ''">$(RuntimeIdentifiers)</InstallerRuntimeIdentifiers> + <InstallerRuntimeIdentifier Condition="'$(InstallerRuntimeIdentifier)' == ''">$(RuntimeIdentifier)</InstallerRuntimeIdentifier> + </PropertyGroup> + + <Import Project="$(MSBuildThisFileDirectory)installer.singlerid.targets" + Condition="'$(MSBuildProjectExtension)' != '.bundleproj' and '$(GenerateInstallers)' == 'true' and '$(InstallerRuntimeIdentifier)' != ''" /> + <Import Project="$(MSBuildThisFileDirectory)installer.multirid.targets" + Condition="'$(MSBuildProjectExtension)' != '.bundleproj' and '$(GenerateInstallers)' == 'true' and '$(InstallerRuntimeIdentifier)' == ''" /> + <Import Project="$(MSBuildThisFileDirectory)bundle.targets" + Condition="'$(MSBuildProjectExtension)' == '.bundleproj'" /> +</Project> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/resources/dotnetbackground.png b/src/Microsoft.DotNet.Build.Tasks.Installers/build/resources/dotnetbackground.png new file mode 100644 index 00000000000..16a6bf22ba7 Binary files /dev/null and b/src/Microsoft.DotNet.Build.Tasks.Installers/build/resources/dotnetbackground.png differ diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/changelog b/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/changelog new file mode 100644 index 00000000000..e61dbc607cb --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/changelog @@ -0,0 +1,2 @@ +* {DATE} {MAINTAINER_NAME} <{MAINTAINER_EMAIL}> - {PACKAGE_VERSION}-{PACKAGE_REVISION} +- {CHANGELOG_MESSAGE} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/copyright b/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/copyright new file mode 100644 index 00000000000..ba561b5eceb --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/rpm_templates/copyright @@ -0,0 +1,8 @@ +Comment: Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. + +Files: * +Copyright: {COPYRIGHT_TEXT} +License: {LICENSE_NAME} + +License: {LICENSE_NAME} + {LICENSE_TEXT} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/bundle.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/bundle.wxs new file mode 100644 index 00000000000..90c44aad03c --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/bundle.wxs @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" + xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" + xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" + xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> + + <?include "..\variables.wxi" ?> + + <Bundle + Name="$(var.ProductName)" + Manufacturer="$(var.Manufacturer)" + Version="$(var.DisplayVersion)" + UpgradeCode="$(var.UpgradeCode)" + AboutUrl="https://dot.net/core" + Compressed="yes"> + + <bal:Condition Message="#(loc.FailureNotSupportedCurrentOperatingSystem)"> + ((VersionNT > v6.1) OR (VersionNT = v6.1 AND ServicePackLevel >= 1)) + </bal:Condition> + + <?if $(var.Platform)=x64 or $(var.Platform)=arm64?> + <bal:Condition Message="#(loc.FailureNotSupportedX86OperatingSystem)"> + VersionNT64 + </bal:Condition> + <?endif?> + + <!-- + List of bundles that this bundle is an upgrade for. Used to support upgrade from bundles + that were produced before UpdateCode was standardized per major-minor channel. + --> + <?ifdef RelatedDotNetBundleIds?> + <?foreach relatedId in $(var.RelatedDotNetBundleIds)?> + <RelatedBundle Action="Upgrade" Id="$(var.relatedId)"/> + <?endforeach?> + <?endif?> + + <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.Foundation"> + <bal:WixStandardBootstrapperApplication + LicenseFile="$(var.DotNetDummyEulaFile)" + ShowFilesInUse="yes" + ShowVersion="yes" /> + + <PayloadGroupRef Id="DotnetCoreBAPayloads" /> + </BootstrapperApplicationRef> + + <swid:Tag Regid="microsoft.com" InstallPath="[DOTNETHOME]" /> + + <Variable Name="DOTNETHOME" Type="string" Value="[$(var.Program_Files)]dotnet" bal:Overridable="no" /> + + <!-- Variables used solely for localization. --> + <Variable Name="BUNDLEMONIKER" Type="string" Value="$(var.ProductMoniker) ($(var.TargetArchitectureDescription))" bal:Overridable="no" /> + <Variable Name="PRODUCT_NAME" Type="string" Value="$(var.ProductName)" bal:Overridable="no" /> + <Variable Name="LINK_PREREQ_PAGE" Type="string" Value="https://go.microsoft.com/fwlink/?linkid=846817" bal:Overridable="no" /> + + <Chain DisableSystemRestore="yes" ParallelCache="yes"> + <?foreach chainedFile in $(var.ChainedDotNetPackageFiles)?> + <MsiPackage SourceFile="$(var.chainedFile)"> + <MsiProperty Name="DOTNETHOME" Value="[DOTNETHOME]" /> + </MsiPackage> + <?endforeach?> + </Chain> + </Bundle> + + <Fragment> + <PayloadGroup Id="DotnetCoreBAPayloads"> + <Payload Name="thm.xml" Compressed="yes" SourceFile="$(var.BundleThmDir)\bundle.thm" /> + <Payload Name="thm.wxl" Compressed="yes" SourceFile="$(var.BundleThmDir)\bundle.wxl" /> + + <Payload Name="bg.png" Compressed="yes" SourceFile="$(var.DotNetBackgroundPngFile)" /> + + <?foreach LCID in $(var.LcidList)?> + <Payload Id="thm-$(var.LCID)" Compressed="yes" Name="$(var.LCID)\thm.wxl" SourceFile="$(var.BundleThmDir)\theme\$(var.LCID)\bundle.wxl" /> + <?endforeach?> + + <Payload Name='eula.rtf' Compressed='yes' SourceFile='!(wix.WixStdbaLicenseRtf)' /> + </PayloadGroup> + + <CustomTable Id='WixStdbaInformation'> + <Row> + <Data Column='LicenseFile'>eula.rtf</Data> + </Row> + </CustomTable> + </Fragment> + +</Wix> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/dummyEula.rtf b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/dummyEula.rtf new file mode 100644 index 00000000000..ebcd5ac373f --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/bundle/dummyEula.rtf @@ -0,0 +1,237 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} +{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} +{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f376\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f377\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f379\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f380\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f381\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f382\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f383\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f384\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f376\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f377\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f379\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f380\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f381\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f382\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f383\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f384\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f746\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f747\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\f749\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f750\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f751\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f752\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} +{\f753\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f754\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;} +{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);} +{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} +{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} +{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; +\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\*\defchp \fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 +\ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused +Normal Table;}{\s15\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext15 \slink16 \sunhideused \styrsid10564401 header;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 +\sbasedon10 \slink15 \slocked \styrsid10564401 Header Char;}{\s17\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 \slink18 \sunhideused \styrsid10564401 footer;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 +\sbasedon10 \slink17 \slocked \styrsid10564401 Footer Char;}}{\*\rsidtbl \rsid2163051\rsid10564401}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim0}{\info +{\operator Rakesh Ranjan Singh}{\creatim\yr2017\mo5\dy30\hr17\min38}{\revtim\yr2017\mo5\dy30\hr17\min39}{\version2}{\edmins1}{\nofpages1}{\nofwords28}{\nofchars160}{\nofcharsws187}{\vern37}}{\*\userprops {\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85 +995028c_Enabled}\proptype30{\staticval True}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SiteId}\proptype30{\staticval 72f988bf-86f1-41af-91ab-2d7cd011db47}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Ref}\proptype30 +{\staticval https://api.informationprotection.azure.com/api/72f988bf-86f1-41af-91ab-2d7cd011db47}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SetBy}\proptype30{\staticval raksingh@microsoft.com}{\propname MSIP_Label_f42aa342-8706-4288-bd11-e +bb85995028c_SetDate}\proptype30{\staticval 2017-05-30T17:39:57.6592568-07:00}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Name}\proptype30{\staticval General}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Application}\proptype30 +{\staticval Microsoft Azure Information Protection}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Extended_MSFT_Method}\proptype30{\staticval Automatic}{\propname Sensitivity}\proptype30{\staticval General}}{\*\xmlnstbl {\xmlns1 http://schemas +.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701 +\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot10564401 \nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar +\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10564401 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2163051 \chftnsep +\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10564401 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2163051 \chftnsepc +\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10564401 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2163051 \chftnsep +\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10564401 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2163051 \chftnsepc +\par }}\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\headerl \ltrpar \pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\headerr \ltrpar \pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\footerl \ltrpar \pard\plain \ltrpar\s17\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\footerr \ltrpar \pard\plain \ltrpar\s17\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\headerf \ltrpar \pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\footerf \ltrpar \pard\plain \ltrpar\s17\ql \li0\ri0\widctlpar\tqc\tx4680\tqr\tx9360\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10564401 +\par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}} +{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8 +\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10564401 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 +\af31507 \ltrch\fcs0 \insrsid10564401 \hich\af31506\dbch\af31505\loch\f31506 This is a dummy file for Eula as required by \hich\af31506\dbch\af31505\loch\f31506 B\hich\af31506\dbch\af31505\loch\f31506 urn bundle \hich\af31506\dbch\af31505\loch\f31506 +. The bal.WixStandardBootstrapperApplication element must hav a value for exactly one of the LicenseFile or LicenseUrl attributes.}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2163051\charrsid10564401 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd +ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d +7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b +d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52 +fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71 +b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b +fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567 +9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd +79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf +5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2 +d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1 +738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68 +2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac +5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a +b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9 +493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2 +be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f +f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64 +7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e +b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4 +6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd +f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d +7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39 +4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf +1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a +faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2 +67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9 +416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27 +1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b +8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4 +8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65 +2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36 +3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e +3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985 +0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000 +0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000 +000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000 +7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000 +000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000 +000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; +\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; +\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; +\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; +\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; +\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; +\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; +\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; +\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; +\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; +\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; +\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b00a +ec6da6d9d201feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/eula.rtf b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/eula.rtf new file mode 100644 index 00000000000..7f40e11a8ab --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/eula.rtf @@ -0,0 +1,97 @@ +{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033\deflangfe1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Tahoma;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\froman\fprq2\fcharset0 Times New Roman;}{\f3\fswiss\fprq2\fcharset0 Calibri;}} +{\colortbl ;\red0\green0\blue0;\red0\green0\blue255;} +{\*\generator Riched20 10.0.10586}{\*\mmathPr\mnaryLim0\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 +\pard\widctlpar\sb120\sa120\cf1\b\f0\fs24 MICROSOFT SOFTWARE LICENSE TERMS\fs28\par +\fs24 MICROSOFT .NET LIBRARY\fs28\par +\fs19 These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\b0\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 updates,\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 supplements,\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 Internet-based services, and\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 support services\par + +\pard\widctlpar\sb120\sa120\b for this software, unless other terms accompany those items. If so, those terms apply.\par +BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\par +IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW.\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\fs20 1.\b0\f2\fs14\~\~\~\~\b\f0\fs19 INSTALLATION AND USE RIGHTS.\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\kerning0\fs20 a.\b0\f2\fs14\~\~\~\~\b\f0\fs19 Installation and Use.\b0\fs20\~You may install and use any number of copies of the software to design, develop and test your programs.\b\fs19\par +\fs20 b.\b0\f2\fs14\~\~\~\~\b\f0\fs19 Third Party Programs.\b0\fs20\~The software may include third party programs that Microsoft, not the third party, licenses to you under this agreement. Notices, if any, for the third party program are included for your information only.\b\fs19\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\fs20 2.\b0\f2\fs14\~\~\~\~\b\f0\fs19 DATA.\~\kerning0\b0\fs20 The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to improve our products and services.\~You can learn more about data collection and use in the help documentation and the privacy statement at\~{\cf0\f3\fs24{\field{\*\fldinst{HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=528096&clcid=0x409"}}{\fldrslt{\ul\cf2\cf2\ul\f0\fs20 http://go.microsoft.com/fwlink/?LinkId=528096}}}}\f0\fs20 . Your use of the software operates as your consent to these practices.\kerning36\b\fs19\par +\fs20 3.\b0\f2\fs14\~\~\~\~\b\f0\fs20 ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS.\fs19\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\kerning0\fs20 a.\b0\f2\fs14\~\~\~\~\b\f0\fs20 DISTRIBUTABLE CODE.\~\~\b0 The software is comprised of Distributable Code. \ldblquote Distributable Code\rdblquote is code that you are permitted to distribute in programs you develop if you comply with the terms below.\b\fs19\par + +\pard\widctlpar\fi-357\li1077\sb120\sa120\fs20 i.\b0\f2\fs14\~\~\~\~\~\~\b\f0\fs20 Right to Use and Distribute.\b0\fs19\par + +\pard\widctlpar\fi-357\li1434\sb120\sa120\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 You may copy and distribute the object code form of the software.\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs.\fs19\par + +\pard\widctlpar\fi-357\li1077\sb120\sa120\b\fs20 ii.\b0\f2\fs14\~\~\~\~\b\f0\fs20 Distribution Requirements.\b0\~\b For any Distributable Code you distribute, you must\b0\fs19\par + +\pard\widctlpar\fi-357\li1434\sb120\sa120\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 add significant primary functionality to it in your programs;\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 require distributors and external end users to agree to terms that protect it at least as much as this agreement;\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 display your valid copyright notice on your programs; and\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\rquote fees, related to the distribution or use of your programs.\fs19\par + +\pard\widctlpar\fi-357\li1077\sb120\sa120\b\fs20 iii.\b0\f2\fs14\~\~\~\b\f0\fs20 Distribution Restrictions.\b0\~\b You may not\b0\fs19\par + +\pard\widctlpar\fi-357\li1434\sb120\sa120\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 alter any copyright, trademark or patent notice in the Distributable Code;\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 use Microsoft\rquote s trademarks in your programs\rquote names or in a way that suggests your programs come from or are endorsed by Microsoft;\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 include Distributable Code in malicious, deceptive or unlawful programs; or\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that\fs19\par + +\pard\widctlpar\fi-358\li1792\sb120\sa120\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 the code be disclosed or distributed in source code form; or\fs19\par +\f1\fs20\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs20 others have the right to modify it.\fs19\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\b\fs20 4.\b0\f2\fs14\~\~\~\~\b\f0\fs19 SCOPE OF LICENSE.\~\b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\b\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\kerning0\b0\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 work around any technical limitations in the software;\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation;\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 publish the software for others to copy;\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 rent, lease or lend the software;\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 transfer the software or this agreement to any third party; or\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 use the software for commercial software hosting services.\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\b\fs20 5.\b0\f2\fs14\~\~\~\~\b\f0\fs19 BACKUP COPY.\~\b0 You may make one backup copy of the software. You may use it only to reinstall the software.\b\par +\fs20 6.\b0\f2\fs14\~\~\~\~\b\f0\fs19 DOCUMENTATION.\~\b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\b\par +\fs20 7.\b0\f2\fs14\~\~\~\~\b\f0\fs19 EXPORT RESTRICTIONS.\~\b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see\~{\cf0\fs20{\field{\*\fldinst{HYPERLINK www.microsoft.com/exporting }}{\fldrslt{www.microsoft.com/exporting\ul0\cf0}}}}\f0\fs19 .\b\par +\fs20 8.\b0\f2\fs14\~\~\~\~\b\f0\fs19 SUPPORT SERVICES.\~\b0 Because this software is \ldblquote as is,\rdblquote we may not provide support services for it.\b\par +\fs20 9.\b0\f2\fs14\~\~\~\~\b\f0\fs19 ENTIRE AGREEMENT.\~\b0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\b\par +\fs20 10.\b0\f2\fs14\~\~\~\b\f0\fs19 APPLICABLE LAW.\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\kerning0\fs20 a.\b0\f2\fs14\~\~\~\~\b\f0\fs19 United States.\~\b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\b\par +\fs20 b.\b0\f2\fs14\~\~\~\~\b\f0\fs19 Outside the United States. If you acquired the software in any other country, the laws of that country apply.\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\fs20 11.\b0\f2\fs14\~\~\b\f0\fs19 LEGAL EFFECT.\~\b0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\b\par +\fs20 12.\b0\f2\fs14\~\~\b\f0\fs19 DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \ldblquote AS-IS.\rdblquote YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.\par + +\pard\widctlpar\li357\sb120\sa120\kerning0 FOR AUSTRALIA \endash YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS.\b0\par + +\pard\widctlpar\fi-357\li357\sb120\sa120\kerning36\b\fs20 13.\b0\f2\fs14\~\~\b\f0\fs19 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.\par + +\pard\widctlpar\li357\sb120\sa120\kerning0\b0 This limitation applies to\par + +\pard\widctlpar\fi-363\li720\sb120\sa120\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\par +\f1\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\par + +\pard\widctlpar\sb120\sa120 It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\par +\lang9 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\lang1033\par +\lang9 Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais.\lang1033\par +\kerning36\b EXON\'c9RATION DE GARANTIE.\~\b0 Le logiciel vis\'e9 par une licence est offert \'ab tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Microsoft n\rquote accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d\rquote ad\'e9quation \'e0 un usage particulier et d\rquote absence de contrefa\'e7on sont exclues.\b\par +LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES.\~\b0 Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices.\b\par +\kerning0\b0\lang9 Cette limitation concerne :\lang1033\par + +\pard\widctlpar\li720\sb120\f1\lang9\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 tout ce qui est reli\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\lang1033\par + +\pard\widctlpar\li720\sa120\f1\lang9\'b7\f2\fs14\~\~\~\~\~\~\~\~\~\f0\fs19 les r\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d\rquote une autre faute dans la limite autoris\'e9e par la loi en vigueur.\lang1033\par + +\pard\widctlpar\sb120\sa120\lang9 Elle s\rquote applique \'e9galement, m\'eame si Microsoft connaissait ou devrait conna\'eetre l\rquote\'e9ventualit\'e9 d\rquote un tel dommage. Si votre pays n\rquote autorise pas l\rquote exclusion ou la limitation de responsabilit\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\rquote exclusion ci-dessus ne s\rquote appliquera pas \'e0 votre \'e9gard.\lang1033\par +\kerning36\b EFFET JURIDIQUE.\~\b0 Le pr\'e9sent contrat d\'e9crit certains droits juridiques. Vous pourriez avoir d\rquote autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\b\par +\kerning0\fs20\lang1036\~\fs19\lang1033\par + +\pard\widctlpar\cf0\b0\f3\fs24\par +} +� \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/breadcrumbstorefolder.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/breadcrumbstorefolder.wxs new file mode 100644 index 00000000000..006303058b2 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/breadcrumbstorefolder.wxs @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> + <?include "..\Variables.wxi" ?> + <Fragment> + <!-- Set registry keys to allow WER to generate correct dumps. --> + <Component Id="BreadcrumbStoreFolder" Directory="BreadcrumbStore" Guid="{DA957490-EA59-42B9-B9D6-3E80F6FB0ED0}"> + <CreateFolder Directory="BreadcrumbStore"> + <Permission + User="Everyone" + CreateChild="no" + CreateFile="yes" + Delete="no" + DeleteChild="no" + Read="yes" + ReadAttributes="yes" + ReadExtendedAttributes="yes" + ReadPermission="yes" + Traverse="yes" /> + <Permission + User="SYSTEM" + GenericAll="yes" /> + <Permission + User="Administrators" + GenericAll="yes" /> + </CreateFolder> + </Component> + + <DirectoryRef Id="TARGETDIR"> + <Directory Id="CommonAppDataFolder"> + <Directory Id="MicrosoftCommonAppData" Name="Microsoft"> + <Directory Id="NetFrameworkCommonAppData" Name="NetFramework"> + <Directory Id="BreadcrumbStore" Name="BreadcrumbStore" /> + </Directory> + </Directory> + </Directory> + </DirectoryRef> + </Fragment> +</Wix> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.common.wxi b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.common.wxi new file mode 100644 index 00000000000..833f564c880 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.common.wxi @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<Include xmlns="http://schemas.microsoft.com/wix/2006/wi"> + + <Package Compressed="yes" InstallScope="perMachine" InstallerVersion="$(var.InstallerVersion)" /> + + <MajorUpgrade DowngradeErrorMessage="$(var.DowngradeErrorMessage)" Schedule="afterInstallInitialize"/> + + <MediaTemplate CompressionLevel="high" EmbedCab="yes"/> + + <Feature + Id="Provider" + Absent="disallow" + AllowAdvertise="no" + Description="Used for Ref Counting" + Display="hidden" + Level="1" + InstallDefault="local" + Title="RefCounting" + TypicalDefault="install"> + <ComponentRef Id="$(var.DependencyKeyId)" /> + </Feature> + + <Property Id="MSIFASTINSTALL" Value="7" /> + + <WixVariable Id="WixUILicenseRtf" Value="$(var.MicrosoftEula)" /> + + <Property Id="WIXUI_INSTALLDIR" Value="DOTNETHOME"/> + <UIRef Id="WixUI_InstallDir" /> + + <CustomActionRef Id="WixBroadcastEnvironmentChange" /> + +</Include> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.wxs new file mode 100644 index 00000000000..1bf18ed9e2d --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/product.wxs @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> + + <?include "..\variables.wxi" ?> + + <Product + Id="*" + Name="$(var.ProductName)" + Language="$(var.ProductLanguage)" + Version="$(var.ProductVersion)" + Manufacturer="$(var.Manufacturer)" + UpgradeCode="$(var.UpgradeCode)"> + + <?include "product.common.wxi" ?> + + <Feature Id="MainFeature" Title="Main Feature" Level="1"> + <?ifdef InstallFiles ?> + <ComponentGroupRef Id="InstallFiles" /> + <?endif?> + + <?ifdef AuthoredRegistryKeys ?> + <ComponentGroupRef Id="AuthoredRegistryKeys"/> + <?endif?> + + <?ifdef WerRelatedKeys ?> + <ComponentGroupRef Id="WerRelatedKeys"/> + <?endif?> + + <?ifdef BreadcrumbStoreFolder ?> + <ComponentRef Id="BreadcrumbStoreFolder"/> + <?endif?> + + <?ifdef ExtraComponentGroupRefIds ?> + <?foreach extraComponentGroupRefId in $(var.ExtraComponentGroupRefIds)?> + <ComponentGroupRef Id="$(var.extraComponentGroupRefId)"/> + <?endforeach?> + <?endif?> + </Feature> + + <?ifdef ExtraPropertyRefIds ?> + <?foreach extraPropertyRefId in $(var.ExtraPropertyRefIds)?> + <PropertyRef Id="$(var.extraPropertyRefId)" /> + <?endforeach?> + <?endif?> + + </Product> + + <Fragment> + <Directory Id="TARGETDIR" Name="SourceDir"> + <Directory Id="$(var.Program_Files)"> + <Directory Id="DOTNETHOME" Name="dotnet" /> + </Directory> + </Directory> + </Fragment> + +</Wix> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/provider.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/provider.wxs new file mode 100644 index 00000000000..5fe17f2a73c --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/provider.wxs @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:dep="http://schemas.microsoft.com/wix/DependencyExtension"> + <?include "..\variables.wxi" ?> + <Fragment> + <Component Id="$(var.DependencyKeyId)" Directory="TARGETDIR" Win64="no"> + <dep:Provides Key="$(var.DependencyKey)" /> + </Component> + </Fragment> +</Wix> \ No newline at end of file diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/registrykeys.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/registrykeys.wxs new file mode 100644 index 00000000000..5711e2a38ba --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/registrykeys.wxs @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> + + <?include "..\variables.wxi" ?> + + <Fragment> + <ComponentGroup Id="AuthoredRegistryKeys"> + <Component Id="SetupRegistry_x86" Directory="TARGETDIR" Win64="no"> + <RegistryKey Root="HKLM" Key="SOFTWARE\dotnet\Setup\InstalledVersions\$(var.Platform)\$(var.RegKeyProductName)"> + + <?ifdef RegKeyNugetVersionExistence?> + <RegistryValue Action="write" Name="$(var.NugetVersion)" Type="integer" Value="1" KeyPath="yes"/> + <?endif?> + + <?ifdef RegKeyNugetVersionValue?> + <RegistryValue Action="write" Name="Version" Type="string" Value="$(var.NugetVersion)" KeyPath="yes"/> + <?endif?> + + </RegistryKey> + </Component> + </ComponentGroup> + </Fragment> +</Wix> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/werrelatedkeys.wxs b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/werrelatedkeys.wxs new file mode 100644 index 00000000000..997df3d42bd --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/product/werrelatedkeys.wxs @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> + <?include "..\Variables.wxi" ?> + <Fragment> + <!-- Set registry keys to allow WER to genereate correct dumps--> + <ComponentGroup Id="WerRelatedKeys"> + <Component Directory="TARGETDIR"> + <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows\Windows Error Reporting\RuntimeExceptionHelperModules"> + <RegistryValue + Action="write" + Name="[DOTNETHOME]shared\$(var.FrameworkName)\$(var.NugetVersion)\mscordaccore.dll" + Type="integer" + Value="0" + KeyPath="yes"/> + </RegistryKey> + </Component> + + <Component Directory="TARGETDIR"> + <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion\KnownManagedDebuggingDlls"> + <RegistryValue + Action="write" + Name="[DOTNETHOME]shared\$(var.FrameworkName)\$(var.NugetVersion)\mscordaccore.dll" + Type="integer" + Value="0" + KeyPath="yes"/> + </RegistryKey> + </Component> + + <Component Directory="TARGETDIR"> + <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion\KnownFunctionTableDlls"> + <RegistryValue + Action="write" + Name="[DOTNETHOME]shared\$(var.FrameworkName)\$(var.NugetVersion)\mscordaccore.dll" + Type="integer" + Value="0" + KeyPath="yes"/> + </RegistryKey> + </Component> + + <Component Directory="TARGETDIR"> + <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows NT\CurrentVersion\MiniDumpAuxiliaryDlls"> + <RegistryValue + Action="write" + Name="[DOTNETHOME]shared\$(var.FrameworkName)\$(var.NugetVersion)\coreclr.dll" + Type="string" + Value="[DOTNETHOME]shared\$(var.FrameworkName)\$(var.NugetVersion)\mscordaccore.dll" + KeyPath="yes"/> + </RegistryKey> + </Component> + </ComponentGroup> + </Fragment> +</Wix> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/variables.wxi b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/variables.wxi new file mode 100644 index 00000000000..8b781939b73 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/variables.wxi @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<Include xmlns="http://schemas.microsoft.com/wix/2006/wi"> + + <?define Dotnet_ProductVersion = "$(var.BuildVersion)" ?> + <?define Dotnet_BuildVersion = "$(var.BuildVersion)" ?> + + <?define Manufacturer = "Microsoft Corporation" ?> + <?define ProductName = "$(var.ProductMoniker) ($(var.TargetArchitectureDescription))" ?> + <?define ProductLanguage = "1033" ?> + <?define ProductVersion = "$(var.Dotnet_ProductVersion)" ?> + <?define LCID = "$(var.ProductLanguage)"?> + <?define DowngradeErrorMessage = "A newer version is already installed; please uninstall it and re-run setup."?> + + <?define Platform = "$(sys.BUILDARCH)" ?> + + <?if $(var.Platform)=x86?> + <?define Program_Files="ProgramFilesFolder"?> + <?define Win64AttributeValue=no?> + <?elseif $(var.Platform)=x64 or $(var.Platform)=arm64?> + <?define Program_Files="ProgramFiles64Folder"?> + <?define Win64AttributeValue=yes?> + <?else?> + <?error Invalid Platform ($(var.Platform))?> + <?endif?> + + <?define DependencyKey = "$(var.DependencyKeyName)_$(var.BuildVersion)_$(var.Platform)$(var.CrossArchContentsPlatformPart)"?> + <?define DependencyKeyId = "$(var.DependencyKey)" ?> + +</Include> diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/vs/VS.Redist.Common.Component.nuspec.txt b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/vs/VS.Redist.Common.Component.nuspec.txt new file mode 100644 index 00000000000..e9bb74c1aa4 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/vs/VS.Redist.Common.Component.nuspec.txt @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> + <metadata> + <id>$COMPONENT_NAME$</id> + <version>1.0.0</version> + <title>$COMPONENT_NAME$ + Microsoft + Microsoft + https://www.microsoft.com/net/dotnet_library_license.htm + $PROJECT_URL$ + true + $FRIENDLY_NAME$ ($ARCH$) Windows Installer MSI as a .nupkg for internal Visual Studio build consumption + © Microsoft Corporation. All rights reserved. + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets new file mode 100644 index 00000000000..5728c165598 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/build/wix/wix.targets @@ -0,0 +1,518 @@ + + + + + + + + + + + + <_WixIntermediateOutputPath>$(BaseIntermediateOutputPath)wix + + + + 3.14.0.4118 + $(BaseIntermediateOutputPath)WixTools.$(WixVersion)/ + + $(WixToolsDir) + $(WixToolsDir)wix.targets + $(IntermediateOutputPath)/wix/ + $(IntermediateOutputPath)/$(InstallerRuntimeIdentifier)/wix/ + $(IntermediateOutputPath)/lightcommand/ + $(InstallerTargetArchitecture) + + $(MSBuildThisFileDirectory)..\acquisition\acquire-wix\acquire-wix.proj + wix.$(WixVersion).zip + https://dotnetcli.azureedge.net/build/wix/$(WixDownloadFilename) + $(WixToolsDir)$(WixDownloadFilename) + $(WixToolsDir)WixDownload.$(WixVersion).sentinel + + + + + + 5 + + 1996-04-01 + + + + + + + + + + + + + + + + + + $(BundleInstallerUpgradeCodeSeed) $(MajorVersion).$(MinorVersion) $(RuntimeIdentifier) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)output/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_OutInstallerFile>$(_InstallerFile) + + + + + + + <_OutInstallerFile Condition="'$(_OutInstallerFile)' == ''">$(_InstallerFile) + + + + 500 + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(InstallerName.Replace('-', '_')) + + + + + + + + + + + + + + + + + + + + + + + <_wixArgs> + <_wixArgs>$(_wixArgs) -nologo + <_wixArgs>$(_wixArgs) -arch $(MsiArch) + <_wixArgs>$(_wixArgs) -out "$(WixObjDir)" + + <_wixArgs>$(_wixArgs) @(WixExtensions -> '-ext %(Identity)', ' ') + <_wixArgs>$(_wixArgs) @(CandleVariables -> '-d%(Identity)="%(Value)"', ' ') + <_wixArgs>$(_wixArgs) @(WixSrcFile -> '"%(FullPath)"', ' ') + <_wixArgs>$(_wixArgs) @(DirectoryToHarvest -> '"%(WixSourceFile)"', ' ') + + + + + + + + <_wixArgs> + <_wixArgs>$(_wixArgs) -nologo + <_wixArgs>$(_wixArgs) -cultures:en-us + <_wixArgs>$(_wixArgs) -out $(_OutInstallerFile) + + <_wixArgs>$(_wixArgs) @(WixExtensions -> '-ext %(Identity)', ' ') + <_wixArgs>$(_wixArgs) @(WixSrcFile -> '"$(WixObjDir)%(Filename).wixobj"', ' ') + <_wixArgs>$(_wixArgs) @(DirectoryToHarvest -> '"%(WixObjFile)"', ' ') + <_lightCommand>light.exe $(_wixArgs) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(MSBuildThisFileDirectory)..\acquisition\acquire-nuget-exe\acquire-nuget-exe.proj + + https://dist.nuget.org/win-x86-commandline/v3.5.0/nuget.exe + $(BaseIntermediateOutputPath)nuget\ + $(NuGetExeToolDir)NuGet.exe + + + + + + VS.Redist.Common.$(VSInsertionShortComponentName).$(InstallerTargetArchitecture)$(CrossArchContentsBuildPart).$(MajorVersion).$(MinorVersion) + $(ArtifactsNonShippingPackagesDir)$(VSInsertionComponentName).$(Version).nupkg + + + $(MSBuildThisFileDirectory)vs\VS.Redist.Common.Component.nuspec.txt + $(IntermediateOutputPath)vs\VS.Redist.Common.Component.nuspec + + + $(PackProperties)COMPONENT_MSI=$(ComponentMsiFile); + $(PackProperties)ARCH=$(MsiArch); + $(PackProperties)COMPONENT_NAME=$(VSInsertionComponentName); + $(PackProperties)FRIENDLY_NAME=$(ProductBrandName); + $(PackProperties)PROJECT_URL=$(RepositoryUrl); + + + $(PackArgs) $(VsInsertionNuspecFile) + $(PackArgs) -Version $(Version) + $(PackArgs) -OutputDirectory $(ArtifactsNonShippingPackagesDir) + $(PackArgs) -NoDefaultExcludes + $(PackArgs) -NoPackageAnalysis + $(PackArgs) -Properties "$(PackProperties)" + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/BuildFPMToolPreReqs.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/BuildFPMToolPreReqs.cs new file mode 100644 index 00000000000..c956a24e661 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/BuildFPMToolPreReqs.cs @@ -0,0 +1,352 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + /// + /// This task prepares the command line parameters for running a RPM build using FPM tool and also updates the copyright and changelog file tokens. + /// If parses various values from the config json by first reading it into a model and then builds the required string for parameters and passes it back. + /// + /// + public class BuildFPMToolPreReqs : BuildTask + { + [Required] + public string InputDir { get; set; } + [Required] + public string OutputDir { get; set; } + [Required] + public string PackageVersion { get; set; } + [Required] + public string ConfigJsonFile { get; set; } + [Output] + public string FPMParameters { get; set; } + + public override bool Execute() + { + try + { + if (!File.Exists(ConfigJsonFile)) + { + throw new FileNotFoundException($"Expected file {ConfigJsonFile} was not found."); + } + + // Open the Config Json and read the values into the model + TextReader projectFileReader = File.OpenText(ConfigJsonFile); + if (projectFileReader != null) + { + string jsonFileText = projectFileReader.ReadToEnd(); + ConfigJson configJson = JsonConvert.DeserializeObject(jsonFileText); + + // Update the Changelog and Copyright files by replacing tokens with values from config json + UpdateChangelog(configJson, PackageVersion); + UpdateCopyRight(configJson); + + // Build the full list of parameters + FPMParameters = BuildCmdParameters(configJson, PackageVersion); + Log.LogMessage(LogImportance.Normal, "Generated RPM paramters: " + FPMParameters); + } + else + { + throw new IOException($"Could not open the file {ConfigJsonFile} for reading."); + } + } + catch (Exception e) + { + Log.LogErrorFromException(e, true); + } + + return !Log.HasLoggedErrors; + } + + // Update the tokens in the changelog file from the config Json + private void UpdateChangelog(ConfigJson configJson, string package_version) + { + try + { + string changelogFile = Path.Combine(InputDir, "templates", "changelog"); + if (!File.Exists(changelogFile)) + { + throw new FileNotFoundException($"Expected file {changelogFile} was not found."); + } + string str = File.ReadAllText(changelogFile); + str = str.Replace("{PACKAGE_NAME}", configJson.Package_Name); + str = str.Replace("{PACKAGE_VERSION}", package_version); + str = str.Replace("{PACKAGE_REVISION}", configJson.Release.Package_Revision); + str = str.Replace("{URGENCY}", configJson.Release.Urgency); + str = str.Replace("{CHANGELOG_MESSAGE}", configJson.Release.Changelog_Message); + str = str.Replace("{MAINTAINER_NAME}", configJson.Maintainer_Name); + str = str.Replace("{MAINTAINER_EMAIL}", configJson.Maintainer_Email); + // The date format needs to be like Wed May 17 2017 + str = str.Replace("{DATE}", DateTime.UtcNow.ToString("ddd MMM dd yyyy")); + File.WriteAllText(changelogFile, str); + } + catch (Exception e) + { + Log.LogError("Exception while updating the changelog file: " + e.Message); + } + } + + private void UpdateCopyRight(ConfigJson configJson) + { + try + { + // Update the tokens in the copyright file from the config Json + string copyrightFile = Path.Combine(InputDir, "templates", "copyright"); + if (!File.Exists(copyrightFile)) + { + throw new FileNotFoundException($"Expected file {copyrightFile} was not found."); + } + string str = File.ReadAllText(copyrightFile); + str = str.Replace("{COPYRIGHT_TEXT}", configJson.CopyRight); + str = str.Replace("{LICENSE_NAME}", configJson.License.Type); + str = str.Replace("{LICENSE_NAME}", configJson.License.Type); + str = str.Replace("{LICENSE_TEXT}", configJson.License.Full_Text); + File.WriteAllText(copyrightFile, str); + } + catch (Exception e) + { + Log.LogError("Exception while updating the copyright file: " + e.Message); + } + } + + private string BuildCmdParameters(ConfigJson configJson, string package_version) + { + // Parameter list that needs to be passed to FPM tool: + // -s : is the input source type(dir) --Static + // -t : is the type of package(rpm) --Static + // -n : is for the name of the package --JSON + // -v : is the version to give to the package --ARG + // -a : architecture --JSON + // -d : is for all dependent packages. This can be used multiple times to specify the dependencies of the package. --JSON + // --rpm-os : the operating system to target this rpm --Static + // --rpm-changelog : the changelog from FILEPATH contents --ARG + // --rpm-summary : it is the RPM summary that shows in the Title --JSON + // --description : it is the description for the package --JSON + // -p : The actual package name (with path) for your package. --ARG+JSON + // --conflicts : Other packages/versions this package conflicts with provided as CSV --JSON + // --directories : Recursively add directories as being owned by the package. --JSON + // --after-install : FILEPATH to the script to be run after install of the package --JSON + // --after-remove : FILEPATH to the script to be run after package removal --JSON + // --license : the licensing name for the package. This will include the license type in the meta-data for the package, but will not include the associated license file within the package itself. --JSON + // --iteration : the iteration to give to the package. This comes from the package_revision --JSON + // --url : url for this package. --JSON + // --verbose : Set verbose output for FPM tool --Static + // : Add all the folder mappings for packge_root, docs, man pages --Static + + var parameters = new List(); + parameters.Add("-s dir"); + parameters.Add("-t rpm"); + parameters.Add(string.Concat("-n ", configJson.Package_Name)); + parameters.Add(string.Concat("-v ", package_version)); + parameters.Add(string.Concat("-a ", configJson.Control.Architecture)); + + // Build the list of dependencies as -d -d + if (configJson.Rpm_Dependencies != null) + { + IEnumerable dependencies; + + switch (configJson.Rpm_Dependencies) + { + case JArray dependencyArray: + dependencies = dependencyArray.ToObject(); + break; + + case JObject dependencyDictionary: + dependencies = dependencyDictionary + .ToObject>() + .Select(pair => new RpmDependency + { + Package_Name = pair.Key, + Package_Version = pair.Value + }); + break; + + default: + throw new ArgumentException( + "Expected 'rpm_dependencies' to be JArray or JObject, but found " + + configJson.Rpm_Dependencies.Type); + } + + foreach (RpmDependency rpmdep in dependencies) + { + string dependency = ""; + if (rpmdep.Package_Name != "") + { + // If no version is specified then the dependency is just the package without >= check + if (rpmdep.Package_Version == "") + { + dependency = rpmdep.Package_Name; + } + else + { + dependency = string.Concat(rpmdep.Package_Name, " >= ", rpmdep.Package_Version); + } + } + if (dependency != "") + { + parameters.Add(string.Concat("-d ", EscapeArg(dependency))); + } + } + } + + // Build the list of owned directories + if (configJson.Directories != null) + { + foreach (string dir in configJson.Directories) + { + if (dir != "") + { + parameters.Add(string.Concat("--directories ", EscapeArg(dir))); + } + } + } + + parameters.Add("--rpm-os linux"); + parameters.Add(string.Concat("--rpm-changelog ", EscapeArg(Path.Combine(InputDir, "templates", "changelog")))); // Changelog File + parameters.Add(string.Concat("--rpm-summary ", EscapeArg(configJson.Short_Description))); + parameters.Add(string.Concat("--description ", EscapeArg(configJson.Long_Description))); + parameters.Add(string.Concat("--maintainer ", EscapeArg(configJson.Maintainer_Name + " <" + configJson.Maintainer_Email + ">"))); + parameters.Add(string.Concat("--vendor ", EscapeArg(configJson.Vendor))); + parameters.Add(string.Concat("-p ", Path.Combine(OutputDir, configJson.Package_Name + ".rpm"))); + if (configJson.Package_Conflicts != null) parameters.Add(string.Concat("--conflicts ", EscapeArg(string.Join(",", configJson.Package_Conflicts)))); + if (configJson.After_Install_Source != null) parameters.Add(string.Concat("--after-install ", Path.Combine(InputDir, EscapeArg(configJson.After_Install_Source)))); + if (configJson.After_Remove_Source != null) parameters.Add(string.Concat("--after-remove ", Path.Combine(InputDir, EscapeArg(configJson.After_Remove_Source)))); + parameters.Add(string.Concat("--license ", EscapeArg(configJson.License.Type))); + parameters.Add(string.Concat("--iteration ", configJson.Release.Package_Revision)); + parameters.Add(string.Concat("--url ", "\"", EscapeArg(configJson.Homepage), "\"")); + parameters.Add("--verbose"); + + // Map all the payload directories as they need to install on the system + if (configJson.Install_Root != null) parameters.Add(string.Concat(Path.Combine(InputDir, "package_root/="), configJson.Install_Root)); // Package Files + if (configJson.Install_Man != null) parameters.Add(string.Concat(Path.Combine(InputDir, "docs", "host/="), configJson.Install_Man)); // Man Pages + if (configJson.Install_Doc != null) parameters.Add(string.Concat(Path.Combine(InputDir, "templates", "copyright="), configJson.Install_Doc)); // CopyRight File + + return string.Join(" ", parameters); + } + + private string EscapeArg(string arg) + { + var sb = new StringBuilder(); + + bool quoted = ShouldSurroundWithQuotes(arg); + if (quoted) sb.Append("\""); + + for (int i = 0; i < arg.Length; ++i) + { + var backslashCount = 0; + + // Consume All Backslashes + while (i < arg.Length && arg[i] == '\\') + { + backslashCount++; + i++; + } + + // Escape any backslashes at the end of the arg + // This ensures the outside quote is interpreted as + // an argument delimiter + if (i == arg.Length) + { + sb.Append('\\', 2 * backslashCount); + } + + // Escape any preceding backslashes and the quote + else if (arg[i] == '"') + { + sb.Append('\\', (2 * backslashCount) + 1); + sb.Append('"'); + } + + // Output any consumed backslashes and the character + else + { + sb.Append('\\', backslashCount); + sb.Append(arg[i]); + } + } + + if (quoted) sb.Append("\""); + + return sb.ToString(); + } + private bool ShouldSurroundWithQuotes(string argument) + { + // Don't quote already quoted strings + if (argument.StartsWith("\"", StringComparison.Ordinal) && + argument.EndsWith("\"", StringComparison.Ordinal)) + { + return false; + } + + // Only quote if whitespace exists in the string + if (argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n")) + { + return true; + } + return false; + } + + /// + /// Model classes for reading and storing the JSON. + /// + private class ConfigJson + { + public string Maintainer_Name { get; set; } + public string Maintainer_Email { get; set; } + public string Vendor { get; set; } + public string Package_Name { get; set; } + public string Install_Root { get; set; } + public string Install_Doc { get; set; } + public string Install_Man { get; set; } + public string Short_Description { get; set; } + public string Long_Description { get; set; } + public string Homepage { get; set; } + public string CopyRight { get; set; } + public Release Release { get; set; } + public Control Control { get; set; } + public License License { get; set; } + public JContainer Rpm_Dependencies { get; set; } + public List Package_Conflicts { get; set; } + public List Directories { get; set; } + public string After_Install_Source { get; set; } + public string After_Remove_Source { get; set; } + } + + private class Release + { + public string Package_Version { get; set; } + public string Package_Revision { get; set; } + public string Urgency { get; set; } + public string Changelog_Message { get; set; } + } + + private class Control + { + public string Priority { get; set; } + public string Section { get; set; } + public string Architecture { get; set; } + } + + private class License + { + public string Type { get; set; } + public string Full_Text { get; set; } + } + + private class RpmDependency + { + public string Package_Name { get; set; } + public string Package_Version { get; set; } + } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/ExecWithRetries.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/ExecWithRetries.cs new file mode 100644 index 00000000000..3beaa96ec29 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/ExecWithRetries.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Initially copied from https://github.com/dotnet/buildtools/blob/6736870b84e06b75e7df32bb84d442db1b2afa10/src/Microsoft.DotNet.Build.Tasks/ExecWithRetries.cs + +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + /// + /// Run a command and retry if the exit code is not 0. + /// + public class ExecWithRetries : BuildTask, ICancelableTask + { + [Required] + public string Command { get; set; } + + public string WorkingDirectory { get; set; } + + public bool IgnoreStandardErrorWarningFormat { get; set; } + + public int MaxAttempts { get; set; } = 5; + + /// + /// Base, in seconds, raised to the power of the number of retries so far. + /// + public double RetryDelayBase { get; set; } = 6; + + /// + /// A constant, in seconds, added to (base^retries) to find the delay before retrying. + /// + /// The default is -1 to make the first retry instant, because ((base^0)-1) == 0. + /// + public double RetryDelayConstant { get; set; } = -1; + + /// + /// MSBuild message importance to use when logging stdout messages from the command. Default + /// is "High". + /// + public string StandardOutputImportance { get; set; } + + /// + /// MSBuild message importance to use when logging stderr messages from the command. Default + /// is "High". + /// + public string StandardErrorImportance { get; set; } + + private CancellationTokenSource _cancelTokenSource = new CancellationTokenSource(); + + private Exec _runningExec; + + public void Cancel() + { + _runningExec?.Cancel(); + _cancelTokenSource.Cancel(); + } + + public override bool Execute() + { + for (int i = 0; i < MaxAttempts; i++) + { + _runningExec = new Exec + { + BuildEngine = BuildEngine, + Command = Command, + WorkingDirectory = WorkingDirectory, + IgnoreStandardErrorWarningFormat = IgnoreStandardErrorWarningFormat, + StandardOutputImportance = StandardOutputImportance, + StandardErrorImportance = StandardErrorImportance, + LogStandardErrorAsError = false, + IgnoreExitCode = true + }; + + if (!_runningExec.Execute()) + { + Log.LogError("Child Exec task failed to execute."); + break; + } + + int exitCode = _runningExec.ExitCode; + if (exitCode == 0) + { + return true; + } + + string message = $"Exec FAILED: exit code {exitCode} (attempt {i + 1}/{MaxAttempts})"; + + if (i + 1 == MaxAttempts || _cancelTokenSource.IsCancellationRequested) + { + Log.LogError(message); + break; + } + + TimeSpan delay = TimeSpan.FromSeconds( + Math.Pow(RetryDelayBase, i) + RetryDelayConstant); + + Log.LogMessage(LogImportance.High, $"{message} -- Retrying after {delay}..."); + + try + { + Task.Delay(delay, _cancelTokenSource.Token).Wait(); + } + catch (AggregateException e) when (e.InnerException is TaskCanceledException) + { + break; + } + } + return false; + } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateCurrentVersion.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateCurrentVersion.cs new file mode 100644 index 00000000000..3c00c3a5f83 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateCurrentVersion.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + public sealed class GenerateCurrentVersion : BuildTask + { + /// + /// The passed in date that will be used to generate a version. (yyyy-MM-dd format) + /// + [Required] + public string SeedDate { get; set; } + + /// + /// Optional parameter containing the Official Build Id. We'll use this to get the revision number out and use it as BuildNumberMinor. + /// + public string OfficialBuildId { get; set; } + + /// + /// Optional parameter that sets the Padding for the version number. Must be 5 or bigger. + /// + public int Padding { get; set; } + + /// + /// If basing off of internal builds version format is not required, this optional parameter lets you pass in a comparison date. + /// + public string ComparisonDate { get; set; } + + /// + /// The Major Version that will be produced given a SeedDate. + /// + [Output] + public string GeneratedVersion { get; set; } + + /// + /// The Revision number that will be produced from the BuildNumber. + /// + [Output] + public string GeneratedRevision { get; set; } + + private const string DateFormat = "yyyy-MM-dd"; + private const string LastModifiedTimeDateFormat = "yyyy-MM-dd HH:mm:ss.FFFFFFF"; + private readonly CultureInfo enUS = new CultureInfo("en-US"); + + public override bool Execute() + { + // If OfficialBuildId is passed in, then use that to calculate the version and revision. + if (string.IsNullOrEmpty(OfficialBuildId)) + { + GeneratedRevision = "0"; + } + else + { + bool success = SetVersionAndRevisionFromBuildId(OfficialBuildId); + return success; + } + + // Calculating GeneratedVersion + if (Padding == 0) + { + Padding = 5; + } + else if (Padding < 5) + { + Log.LogWarning("The specified Padding '{0}' has to be equal to or greater than 5. Using 5 as a default now.", Padding); + Padding = 5; + } + DateTime date; + GeneratedVersion = string.Empty; + if (!(DateTime.TryParseExact(SeedDate, DateFormat, enUS, DateTimeStyles.AssumeLocal, out date))) + { + // Check if the timestamp matches the LastModifiedTimeDateFormat + if (!(DateTime.TryParseExact(SeedDate, LastModifiedTimeDateFormat, enUS, DateTimeStyles.AssumeLocal, out date))) + { + Log.LogError("The seed date '{0}' is not valid. Please specify a date in the short format.({1})", SeedDate, DateFormat); + return false; + } + } + //Convert Date to UTC to converge + date = date.ToUniversalTime(); + GeneratedVersion = GetCurrentVersionForDate(date, ComparisonDate); + if (string.IsNullOrEmpty(GeneratedVersion)) + { + Log.LogError("The date '{0}' is not valid. Please pass in a date after {1}.", SeedDate, ComparisonDate); + return false; + } + return true; + } + + public bool SetVersionAndRevisionFromBuildId(string buildId) + { + Regex regex = new Regex(@"(\d{8})[\-\.](\d+)$"); + string dateFormat = "yyyyMMdd"; + Match match = regex.Match(buildId); + if (match.Success && match.Groups.Count > 2) + { + DateTime buildIdDate; + if (!DateTime.TryParseExact(match.Groups[1].Value, dateFormat, enUS, DateTimeStyles.AssumeLocal, out buildIdDate)) + { + Log.LogError("The OfficialBuildId doesn't follow the expected({0}.rr) format: '{1}'", dateFormat, match.Groups[1].Value); + return false; + } + buildIdDate = buildIdDate.ToUniversalTime(); + GeneratedVersion = GetCurrentVersionForDate(buildIdDate, ComparisonDate); + GeneratedRevision = match.Groups[2].Value; + return true; + } + Log.LogError("Error: Invalid OfficialBuildId was passed: '{0}'", buildId); + return false; + } + + public string GetCurrentVersionForDate(DateTime seedDate, string comparisonDate) + { + DateTime compareDate; + if (string.IsNullOrEmpty(comparisonDate)) + { + /* + * We need to ensure that our build numbers are higher that what we used to ship internal builds so this date + * will make that possible. + */ + compareDate = new DateTime(1996, 4, 1, 0, 0, 0, DateTimeKind.Utc); + } + else + { + bool isValidDate = DateTime.TryParseExact(comparisonDate, DateFormat, enUS, DateTimeStyles.AssumeLocal, out compareDate); + if (!isValidDate) + { + Log.LogError("The comparison date '{0}' is not valid. Please specify a date in the short format.({1})", comparisonDate, DateFormat); + } + //Convert to UTC to converge + compareDate = compareDate.ToUniversalTime(); + } + int months = (seedDate.Year - compareDate.Year) * 12 + seedDate.Month - compareDate.Month; + if (months > 0) //only allow dates after comparedate + { + return string.Format("{0}{1}", months.ToString("D" + (Padding - 2)), seedDate.Day.ToString("D2")); + } + return string.Empty; + } + + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateGuidFromName.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateGuidFromName.cs new file mode 100644 index 00000000000..678b32a61a0 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateGuidFromName.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using System; +using System.Security.Cryptography; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + public class GenerateGuidFromName : BuildTask + { + [Required] + public string Name { get; set; } + + [Output] + public string GeneratedGuid { get; set; } + + // Generate a Version 5 (SHA1 Name Based) Guid from a name. + public override bool Execute() + { + // Any fixed GUID will do for a namespace. + Guid namespaceId = new Guid("28F1468D-672B-489A-8E0C-7C5B3030630C"); + + using (SHA1 hasher = SHA1.Create()) + { + var nameBytes = System.Text.Encoding.UTF8.GetBytes(Name ?? string.Empty); + var namespaceBytes = namespaceId.ToByteArray(); + + SwapGuidByteOrder(namespaceBytes); + + var streamToHash = new byte[namespaceBytes.Length + nameBytes.Length]; + + Array.Copy(namespaceBytes, streamToHash, namespaceBytes.Length); + Array.Copy(nameBytes, 0, streamToHash, namespaceBytes.Length, nameBytes.Length); + + var hashResult = hasher.ComputeHash(streamToHash); + + var res = new byte[16]; + + Array.Copy(hashResult, res, res.Length); + + unchecked { res[6] = (byte)(0x50 | (res[6] & 0x0F)); } + unchecked { res[8] = (byte)(0x40 | (res[8] & 0x3F)); } + + SwapGuidByteOrder(res); + + GeneratedGuid = (new Guid(res)).ToString(); + } + + return true; + } + + // Do a byte order swap, .NET GUIDs store multi byte components in little + // endian. + private static void SwapGuidByteOrder(byte[] b) + { + Swap(b, 0, 3); + Swap(b, 1, 2); + Swap(b, 5, 6); + Swap(b, 7, 8); + } + + private static void Swap(byte[] b, int x, int y) + { + byte t = b[x]; + b[x] = b[y]; + b[y] = t; + } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateJsonObjectString.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateJsonObjectString.cs new file mode 100644 index 00000000000..189b1b3d0eb --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateJsonObjectString.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + public class GenerateJsonObjectString : BuildTask + { + private static readonly string __indent1 = new string(' ', 4); + private static readonly string __indent2 = new string(' ', 8); + + /// + /// Properties to include. If multiple properties have the same name, each property value is + /// included in an array. Only specify one value metadata: the first value is used. + /// + /// %(Identity): Name of the property. + /// %(String): String value of the property. This task adds quotes around it in the JSON. + /// %(Object): Object value of the property. Create this with a nested call to this task. + /// + [Required] + public ITaskItem[] Properties { get; set; } + + /// + /// If set, also write the output JSON string to this file. + /// + public string TargetFile { get; set; } + + [Output] + public string Json { get; set; } + + public override bool Execute() + { + var result = new StringBuilder(); + result.AppendLine("{"); + + bool firstProperty = true; + + foreach (var group in Properties.GroupBy(item => item.ItemSpec)) + { + if (firstProperty) + { + firstProperty = false; + } + else + { + result.AppendLine(","); + } + + result.Append(__indent1); + result.Append("\""); + result.Append(group.Key); + result.Append("\": "); + + if (group.Count() == 1) + { + ITaskItem item = group.First(); + WriteProperty(result, item, __indent1); + } + else + { + result.AppendLine("["); + + bool firstArrayLine = true; + + foreach (ITaskItem item in group) + { + if (firstArrayLine) + { + firstArrayLine = false; + } + else + { + result.AppendLine(","); + } + + result.Append(__indent2); + WriteProperty(result, item, __indent2); + } + + result.AppendLine(); + result.Append(__indent1); + result.Append("]"); + } + } + + result.AppendLine(); + result.AppendLine("}"); + + Json = result.ToString(); + + if (!string.IsNullOrEmpty(TargetFile)) + { + Directory.CreateDirectory(Path.GetDirectoryName(TargetFile)); + File.WriteAllText(TargetFile, Json); + } + + return !Log.HasLoggedErrors; + } + + private void WriteProperty(StringBuilder result, ITaskItem item, string indent) + { + string stringValue = item.GetMetadata("String"); + string objectValue = item.GetMetadata("Object"); + + if (!string.IsNullOrEmpty(stringValue)) + { + result.Append("\""); + result.Append(stringValue); + result.Append("\""); + } + else if (!string.IsNullOrEmpty(objectValue)) + { + bool firstObjectLine = true; + + foreach (var line in objectValue.Split( + new[] {Environment.NewLine}, + StringSplitOptions.RemoveEmptyEntries)) + { + if (firstObjectLine) + { + firstObjectLine = false; + } + else + { + result.AppendLine(); + result.Append(indent); + } + + result.Append(line); + } + } + else + { + Log.LogError($"Item '{item.ItemSpec}' has no String or Object value."); + result.Append("null"); + } + } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMacOSDistributionFile.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMacOSDistributionFile.cs new file mode 100644 index 00000000000..434a522218b --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMacOSDistributionFile.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.DotNet.Build.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class GenerateMacOSDistributionFile : BuildTask + { + [Required] + public string TemplatePath { get; set; } + + [Required] + public string ProductBrandName { get; set; } + + [Required] + public string TargetArchitecture { get; set; } + + [Required] + public ITaskItem[] BundledPackages { get; set; } + + [Required] + public string DestinationFile { get; set; } + + public override bool Execute() + { + try + { + XDocument document = XDocument.Load(TemplatePath); + + var titleElement = new XElement("title", $"{ProductBrandName} ({TargetArchitecture})"); + + var choiceLineElements = BundledPackages.Select(component => new XElement("line", new XAttribute("choice", component.GetMetadata("FileNameWithExtension")))); + + var choiceElements = BundledPackages + .Select(component => new XElement("choice", + new XAttribute("id", component.GetMetadata("FileNameWithExtension")), + new XAttribute("visible", "true"), + new XAttribute("title", component.GetMetadata("Title")), + new XAttribute("description", component.GetMetadata("Description")), + new XElement("pkg-ref", new XAttribute("id", component.GetMetadata("FileNameWithExtension"))))); + + var pkgRefElements = BundledPackages + .Select(component => new XElement("pkg-ref", + new XAttribute("id", component.GetMetadata("FileNameWithExtension")), + component.GetMetadata("FileNameWithExtension"))); + + document.Root.Add(new XElement("choices-outline", choiceLineElements)); + document.Root.Add(choiceElements); + document.Root.Add(pkgRefElements); + using XmlWriter writer = XmlWriter.Create(File.OpenWrite(DestinationFile)); + document.WriteTo(writer); + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, false); + return false; + } + return true; + } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMsiVersion.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMsiVersion.cs new file mode 100644 index 00000000000..fefcb0a13a5 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/GenerateMsiVersion.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + // MSI versioning + // Encode the CLI version to fit into the MSI versioning scheme - https://msdn.microsoft.com/en-us/library/windows/desktop/aa370859(v=vs.85).aspx + // MSI versions are 3 part + // major.minor.build + // Size(bits) of each part 8 8 16 + // So we have 32 bits to encode the CLI version + + // For a CLI version based on commit count: + // Starting with most significant bit this how the CLI version is going to be encoded as MSI Version + // CLI major -> 6 bits + // CLI minor -> 6 bits + // CLI patch -> 6 bits + // CLI commitcount -> 14 bits + // + // For a CLI version based on BuildTools versioning + // CLI major -> 5 bits + // CLI minor -> 5 bits + // CLI patch -> 4 bits + // BuildNumber major -> 14 bits + // BuildNumber minor -> 4 bits + public class GenerateMsiVersion : BuildTask + { + [Required] + public string Major { get; set; } + [Required] + public string Minor { get; set; } + [Required] + public string Patch { get; set; } + public string BuildNumber { get; set; } + public string BuildNumberMajor { get; set; } + public string BuildNumberMinor { get; set; } + [Output] + public string MsiVersion { get; set; } + + public override bool Execute() + { + if(BuildNumber == null && BuildNumberMajor == null) + { + Log.LogError("Either BuildNumber or BuildNumberMajor and BuildNumberMinor are required parameters."); + return false; + } + if(BuildNumber != null && BuildNumberMajor != null) + { + Log.LogError("You must specify either BuildNumber or BuildNumberMajor and BuildNumberMinor, you cannot specify both parameters."); + return false; + } + if (BuildNumberMajor != null && BuildNumberMinor == null) + { + Log.LogError("If you specify a BuildNumberMajor, you must also specify the BuildNumberMinor."); + return false; + } + if(BuildNumber != null) + { + ParseBuildNumber(); + } + else + { + ParseBuildNumberMajorMinor(); + } + return true; + } + private void ParseBuildNumber() + { + var major = int.Parse(Major) << 26; + var minor = int.Parse(Minor) << 20; + var patch = int.Parse(Patch) << 14; + var msiVersionNumber = major | minor | patch | int.Parse(BuildNumber); + + var msiMajor = (msiVersionNumber >> 24) & 0xFF; + var msiMinor = (msiVersionNumber >> 16) & 0xFF; + var msiBuild = msiVersionNumber & 0xFFFF; + + MsiVersion = $"{msiMajor}.{msiMinor}.{msiBuild}"; + } + private void ParseBuildNumberMajorMinor() + { + var major = int.Parse(Major) << 27; + var minor = int.Parse(Minor) << 22; + var patch = int.Parse(Patch) << 18; + + var buildNumberMajor = int.Parse(BuildNumberMajor) & 0x3FFF << 4; + var buildNumberMinor = int.Parse(BuildNumberMinor) & 0xF; + + var msiVersionNumber = major | minor | patch | buildNumberMajor | buildNumberMinor; + + var msiMajor = (msiVersionNumber >> 24) & 0xFF; + var msiMinor = (msiVersionNumber >> 16) & 0xFF; + var msiBuild = msiVersionNumber & 0xFFFF; + + MsiVersion = $"{msiMajor}.{msiMinor}.{msiBuild}"; + } + + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Installers/src/StabilizeWixFileId.cs b/src/Microsoft.DotNet.Build.Tasks.Installers/src/StabilizeWixFileId.cs new file mode 100644 index 00000000000..a0a2f1fa1c3 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Installers/src/StabilizeWixFileId.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.DotNet.Build.Tasks.Installers +{ + /// + /// In a WiX source file, replaces the Id of a File with some given string in order to stabilize + /// it. This allows external tooling such as signature validators to rely on a stable identifier + /// for certain files. + /// + public class StabilizeWixFileId : BuildTask + { + /// + /// File to read from. This is expected to be an output from heat.exe. + /// + /// Expected format: + /// + /// + /// + /// + /// + /// + /// + /// ... + /// + [Required] + public string SourceFile { get; set; } + + /// + /// File to write to. May be the same as SourceFile. + /// + [Required] + public string OutputFile { get; set; } + + /// + /// Set of files to stabilize. This matches the end of the "Source" attribute in the WiX + /// source file. If exactly one match isn't found in the WiX source file, this task fails. + /// + /// %(Identity): The file source to replace. + /// %(ReplacementId): The replacement for Id that won't change per-build. + /// + [Required] + public ITaskItem[] FileElementToStabilize { get; set; } + + public override bool Execute() + { + XDocument content = XDocument.Load(SourceFile); + + XNamespace rootNamespace = content.Root.GetDefaultNamespace(); + XName GetQualifiedName(string name) => rootNamespace.GetName(name); + + foreach (var file in FileElementToStabilize) + { + string replacement = file.GetMetadata("ReplacementId"); + + if (string.IsNullOrEmpty(replacement)) + { + Log.LogError($"{nameof(FileElementToStabilize)} {file.ItemSpec} has null/empty ReplacementId metadata."); + continue; + } + + XElement[] matchingFileElements = content.Element(GetQualifiedName("Wix")) + .Elements(GetQualifiedName("Fragment")) + .SelectMany(f => f.Elements(GetQualifiedName("ComponentGroup"))) + .SelectMany(cg => cg.Elements(GetQualifiedName("Component"))) + .SelectMany(c => c.Elements(GetQualifiedName("File"))) + .Where(f => f.Attribute("Source")?.Value + ?.EndsWith(file.ItemSpec, StringComparison.OrdinalIgnoreCase) == true) + .ToArray(); + + if (matchingFileElements.Length != 1) + { + Log.LogError( + $"Expected 1 match for '{file.ItemSpec}', found {matchingFileElements.Length}: " + + string.Join(", ", matchingFileElements.Select(e => e.ToString()))); + + continue; + } + + XAttribute nameAttribute = matchingFileElements[0].Attribute("Id"); + + if (nameAttribute is null) + { + Log.LogError($"Match has no Id attribute: {matchingFileElements[0]}"); + continue; + } + + Log.LogMessage( + $"Setting '{file.ItemSpec}' Id to '{replacement}' for File with Source " + + matchingFileElements[0].Attribute("Source").Value); + + nameAttribute.Value = replacement; + } + + content.Save(OutputFile); + + return !Log.HasLoggedErrors; + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj b/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj new file mode 100644 index 00000000000..c00379a435b --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/Microsoft.DotNet.SharedFramework.Sdk.csproj @@ -0,0 +1,54 @@ + + + + net472;netcoreapp2.1 + preview + false + + true + + Common toolset for building shared frameworks and framework packs. + MSBuildSdk + + false + false + $(NoWarn);3021;NU5105 + + **/*.Desktop.* + + + + + + + + + + + + + + + sdk/%(RecursiveDir)%(Filename)%(Extension) + + + targets + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/README.md b/src/Microsoft.DotNet.SharedFramework.Sdk/README.md new file mode 100644 index 00000000000..ed2931fba1a --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/README.md @@ -0,0 +1,144 @@ +# Microsoft.DotNet.SharedFramework.Sdk + +This package provides a common set of tools for authoring framework packs, shared frameworks, and packages and installers for packs and frameworks. + +## Building Framework Packs + +This SDK supports building framework packs based on the [official design](https://github.com/dotnet/designs/blob/master/accepted/2019/targeting-packs-and-runtime-packs.md) as well as additional extensions such as profiles and single-file support. To define a shared framework project, create a project with the `.sfxproj` suffix that references both the `Microsoft.NET.Sdk` and `Microsoft.DotNet.SharedFramework.Sdk` SDKs and defines the name in the `SharedFrameworkName` property and the pack type in the `PlatformPackageType` property. + +An example project is included below: + +```xml + + + + + netcoreapp3.0 + win-x64 + Microsoft.Banana.App + RuntimePack + true + + + + + + + + + + + + + +``` + +This example project shows some of the basic options provided by the SDK. The `SharedFrameworkName` property specifies the name of the shared framework. This is used to determine the NuGet package ID as well as appearing in various data files generated by the build. The `PlatformPackageType` property specifies the type of pack the project defines. There are four types of packs this SDK supports: + +- `RuntimePack`: RID-specific runtime packages +- `TargetingPack`: Packs of reference assemblies +- `AppHostPack`: Pack containing the app hosts +- `ToolPack`: This pack option allows you to have completely custom scripting for what files are included where in your pack. It also enables creating installer-only projects with this SDK. + +This SDK uses the standard mechanisms in the .NET SDK for referencing packages, projects, and other frameworks to resolve its dependencies and collect the files that are a part of the pack. To include native files, add them to the `NativeRuntimeAsset` item group. + +If you need to resolve additional managed assemblies, you can add them to the `ReferenceCopyLocalPaths` item group for runtime packs or `ReferencePath` for targeting packs. XML documentation files can be added to the `DocFilesToPackage` item group. If you need to resolve your assemblies in a target, you can define your own target to resolve them. To enable these files to have ReadyToRun code generated for them for a runtime pack, update the `GetSharedFrameworkFilesForReadyToRunDependsOn` property to include your targets in a semicolon-delimited list. If you are adding additional files to a targeting pack, add `BeforeTargets="GetFilesToPackage"` to the target definition. + +### Deps file generation + +By default, the SDK will generate a .deps.json file for runtime packs named `$(SharedFrameworkName).deps.json`. You can set the `$(SharedFrameworkHostFileNameOverride)` property to instead generate the deps file with the name `$(SharedFrameworkHostFileNameOverride).deps.json`. + +### Runtimeconfig file generation + +This SDK uses the built-in .NET SDK generation for .runtimeconfig.json files for runtime packs. By default, the output file will be named `$(SharedFrameworkName).runtimeconfig.json`. You can override it to be named `$(SharedFrameworkHostFileNameOverride).runtimeconfig.json`. + +### Platform Manifest generation + +Since platform manifest generation can be tricky at times when a shared framework has differing files per platform, this SDK provides two different mechanisms for generating a platform manifest, templating or harvesting. + +Pros of harvesting: + +- Simple declaration in the project file. + - You define a property in the targeting pack project that points to the runtime pack project. The SDK will determine all of the files that the runtime pack project produces on RIDs that this build will produce. +- Less friction when adding files to the manifest. + - Files will be automatically added by the harvesting process. + +Cons of harvesting: + +- Either all runtime pack RIDs must be buildable from a single invocation or one RID must have a superset of all files in the targeting pack. + +Pros of templating: + +- All files can be declared to be in the manifest without needing every file on disk. + - As a result, this enables building a targeting pack for a runtime pack that has disjoint sets of files on different RIDs, such as Microsoft.NETCore.App, without needing to download any runtime pack builds. +- Targeting pack can be built independently of the runtime pack. +- Ensures that every file in a runtime pack is a known file in the manifest. + +Cons of templating: + +- Each file has to be declared with an MSBuild item. +- File and assembly fallback versions need to be declared for files that do not exist in the targeting pack. +- Any change in the list of files requires a new MSBuild item declaring the entry. + +### Platform Manifest Harvesting + +For platforms where all target platforms have the same files or where one platform has a superset of the files available on all platforms, we can generate a platform manifest from the present files in the runtime pack. + +This is enabled by default or when `UseTemplatedPlatformManifest` is not set to `true`. + +Set the `RuntimePackProjectPath` to the path to the runtime pack shared framework project. + +If you only want to use one RID to generate the platform manifest, you can set the `RuntimePackPlatformForManifest` property to the RID you'd like to use. If the chosen RID doesn't have a superset of files for all shipping platforms, then you may have unexpected behavior when using the produced targeting pack. + +### Templated Platform Manifest + +For shared frameworks with differing sets of files on various platforms and with no single platform that contains all of the files, this SDK provides support for creating a platform manifest from a templated list of items. + +Assumptions: + +- All managed assemblies are either present in both the ref and runtime pack for the shared framework + or have an easily calculatable assembly version and file version. +- All native files have an easily calculatable file version. + +To use, set `UseTemplatedPlatformManifest` to true and define a set of `PlatformManifestFileEntry` items + +`PlatformManifestFileEntry` metadata: + +- `ItemSpec`/`Identity` + - File name and extension of file in the shared framework. +- `IsNative` + - true when the file is a native file +- `FallbackAssemblyVersion` + - An assembly version for this file if it is not present in the ref-pack build. +- `FallbackFileVersion` + - A file version for this file if it is not present in the ref-pack build. + +Properties for these targets: + +- `UseTemplatedPlatformManifest`: Set to true to enable the templated platform manifest generation +- `PlatformManifestFallbackAssemblyVersion`: Fallback asssembly version when one is needed and there is no fallback on the entry. +- `PlatformManifestFallbackFileVersion`: Fallback file version when one is needed and there is no fallback on the entry. + +### RuntimeList/FrameworkList Generation + +The FrameworkList/RuntimeList support in this SDK has some additions on the base design to support profile and single-file features. + +If your shared framework has various profiles, you can use `` items to specify the profiles for each file in the shared framework. However, once you've specified one `FrameworkListFileClass` entry, you need to specify one for every file in the framework. + +If your shared framework can drop some files from the single-file bundle, you can specify `` items that specify which files by name are **included** in the single file bundle. Once one `SingleFileHostIncludeFilename` item is specified, all files without corresponding `SingleFileHostIncludeFilename` elements will be marked as droppable from a single file build. + +The name of the framework/runtime is specified in the `SharedFrameworkFriendlyName` property, which defaults to the value of `SharedFrameworkName`. + +### Archives + +By default, this SDK also generates an archive that can be extracted on top of an existing .NET SDK or Runtime layout. The name of this file is derived from the `SharedFrameworkArchiveName` property and the RID. By default, `SharedFrameworkArchiveName` is set to the value of `SharedFrameworkName`. + +### Custom dumping to disk + +This SDK provides a custom target named `PublishToDisk` that publishes the generated framework pack to disk as though the folder specified via the `OutputPath` is the root of a .NET SDK or Runtime layout. This target is provided for compatibilty with the `Microsoft.DotNet.Build.Tasks.Installers` and `Microsoft.DotNet.Build.Tasks.Archives` packages, but can also be used to implement custom dumping to disk to set up test harnesses with the shared framework. + +## Build Skip Support for Unsupported Platforms and Servicing + +This SDK also supports automatically skipping builds on unsupported platforms or in servicing releases. If a project with a list of provided RIDs in `RuntimeIdentifiers` is built with the `RuntimeIdentifier` property set to a RID that is not in the `RuntimeIdentifiers` list, the build will be skipped. This enables cleanly skipping optional packs, installers, or bundles that only exist on specific platforms. + +Additionally, if a `ProjectServicingConfiguration` item is provided with the identity of the project name and the `PatchVersion` metadata on the item is not equal to the current `PatchVersion`, the build will be skipped. This support enables a repository to disable building targeting packs in servicing releases if that is desired. diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.props b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.props new file mode 100644 index 00000000000..a3dce90f91e --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.props @@ -0,0 +1,26 @@ + + + + + + $(MSBuildThisFileDirectory)../tools/netcoreapp2.1/ + $(MSBuildThisFileDirectory)../tools/net472/ + + + + $(DotNetSharedFrameworkTaskDir)Microsoft.DotNet.SharedFramework.Sdk.dll + + + + + $(MSBuildThisFileDirectory)..\targets\ + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.targets new file mode 100644 index 00000000000..235e499fdb0 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/sdk/Sdk.targets @@ -0,0 +1,22 @@ + + + + + + true + + + + <_DispatchMultiRidBuild Condition="'$(IsCrossTargetingBuild)' != 'true' or '$(IsCrossTargetedBuild)' == 'true'">$(IsMultiRidBuild) + + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/CreateFrameworkListFile.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/CreateFrameworkListFile.cs new file mode 100644 index 00000000000..8e46d1f9449 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/CreateFrameworkListFile.cs @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.DotNet.Build.Tasks; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class CreateFrameworkListFile : BuildTask + { + /// + /// Files to extract basic information from and include in the list. + /// + [Required] + public ITaskItem[] Files { get; set; } + + /// + /// A list of assembly names with classification info such as Profile. A + /// Profile="%(Profile)" attribute is included in the framework list for the matching Files + /// item if %(Profile) contains text. + /// + /// If *any* FileClassifications are passed: + /// + /// *Every* file that ends up listed in the framework list must have a matching + /// FileClassification. + /// + /// Additionally, every FileClassification must find exactly one File. + /// + /// This task fails if the conditions aren't met. This ensures the classification doesn't + /// become out of date when the list of files changes. + /// + /// %(Identity): Assembly name (including ".dll"). + /// %(Profile): List of profiles that apply, semicolon-delimited. + /// %(ReferencedByDefault): Empty (default) or "true"/"false". Indicates whether this file + /// should be referenced by default when the SDK uses this framework. + /// + public ITaskItem[] FileClassifications { get; set; } + + [Required] + public string TargetFile { get; set; } + + public string[] TargetFilePrefixes { get; set; } + + public string[] SingleFileHostIncludeFilenames { get; set; } + + /// + /// Extra attributes to place on the root node. + /// + /// %(Identity): Attribute name. + /// %(Value): Attribute value. + /// + public ITaskItem[] RootAttributes { get; set; } + + public override bool Execute() + { + XAttribute[] rootAttributes = RootAttributes + ?.Select(item => new XAttribute(item.ItemSpec, item.GetMetadata("Value"))) + .ToArray(); + + var frameworkManifest = new XElement("FileList", rootAttributes); + + Dictionary fileClassLookup = FileClassifications + ?.ToDictionary( + item => item.ItemSpec, + item => item, + StringComparer.OrdinalIgnoreCase); + + var singleFileHostIncludeFilenames = SingleFileHostIncludeFilenames?.ToHashSet(); + + var usedFileClasses = new HashSet(); + + foreach (var f in Files + .Where(IsTargetPathIncluded) + .Select(item => new + { + Item = item, + Filename = Path.GetFileName(item.ItemSpec), + TargetPath = item.GetMetadata("TargetPath"), + AssemblyName = FileUtilities.GetAssemblyName(item.ItemSpec), + FileVersion = FileUtilities.GetFileVersion(item.ItemSpec), + IsNative = item.GetMetadata("IsNative") == "true", + IsSymbolFile = item.GetMetadata("IsSymbolFile") == "true", + IsResourceFile = item.ItemSpec + .EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase) + }) + .Where(f => + !f.IsSymbolFile && + (f.Filename.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || f.IsNative)) + // Remove duplicate files this task is given. + .GroupBy(f => f.Item.ItemSpec) + .Select(g => g.First()) + // Make order stable between builds. + .OrderBy(f => f.TargetPath, StringComparer.Ordinal) + .ThenBy(f => f.Filename, StringComparer.Ordinal)) + { + string type = "Managed"; + + if (f.IsNative) + { + type = "Native"; + } + else if (f.IsResourceFile) + { + type = "Resources"; + } + + string path = Path.Combine(f.TargetPath, f.Filename).Replace('\\', '/'); + + if (path.StartsWith("runtimes/")) + { + var pathParts = path.Split('/'); + if (pathParts.Length > 1 && pathParts[1].Contains("_")) + { + // This file is a runtime file with a "rid" containing "_". This is assumed + // to mean it's a cross-targeting tool and shouldn't be deployed in a + // self-contained app. Leave it off the list. + continue; + } + } + + var element = new XElement( + "File", + new XAttribute("Type", type), + new XAttribute("Path", path)); + + if (f.IsResourceFile) + { + element.Add( + new XAttribute("Culture", Path.GetFileName(Path.GetDirectoryName(path)))); + } + + if (f.AssemblyName != null) + { + byte[] publicKeyToken = f.AssemblyName.GetPublicKeyToken(); + string publicKeyTokenHex; + + if (publicKeyToken != null) + { + int len = publicKeyToken.Length; + StringBuilder publicKeyTokenBuilder = new StringBuilder(len * 2); + for (int i = 0; i < len; i++) + { + publicKeyTokenBuilder.Append(publicKeyToken[i].ToString("x", CultureInfo.InvariantCulture)); + } + publicKeyTokenHex = publicKeyTokenBuilder.ToString(); + } + else + { + Log.LogError($"No public key token found for assembly {f.Item.ItemSpec}"); + publicKeyTokenHex = ""; + } + + element.Add( + new XAttribute("AssemblyName", f.AssemblyName.Name), + new XAttribute("PublicKeyToken", publicKeyTokenHex), + new XAttribute("AssemblyVersion", f.AssemblyName.Version)); + } + else if (!f.IsNative) + { + // This file isn't managed and isn't native. Leave it off the list. + continue; + } + + element.Add(new XAttribute("FileVersion", f.FileVersion)); + + if (fileClassLookup != null) + { + if (fileClassLookup.TryGetValue(f.Filename, out ITaskItem classItem)) + { + string profile = classItem.GetMetadata("Profile"); + + if (!string.IsNullOrEmpty(profile)) + { + element.Add(new XAttribute("Profile", profile)); + } + + string referencedByDefault = classItem.GetMetadata("ReferencedByDefault"); + + if (!string.IsNullOrEmpty(referencedByDefault)) + { + element.Add(new XAttribute("ReferencedByDefault", referencedByDefault)); + } + + usedFileClasses.Add(f.Filename); + } + else + { + Log.LogError($"File matches no classification: {f.Filename}"); + } + } + + if (f.IsNative) + { + // presence of inclusion list indicates that + // all other native files should be marked as "DropFromSingleFile" + if (singleFileHostIncludeFilenames != null && + !singleFileHostIncludeFilenames.Contains(f.Filename)) + { + element.Add(new XAttribute("DropFromSingleFile", "true")); + } + } + + frameworkManifest.Add(element); + } + + foreach (var unused in fileClassLookup + ?.Keys.Except(usedFileClasses).OrderBy(p => p) + ?? Enumerable.Empty()) + { + Log.LogError($"Classification matches no files: {unused}"); + } + + Directory.CreateDirectory(Path.GetDirectoryName(TargetFile)); + File.WriteAllText(TargetFile, frameworkManifest.ToString()); + + return !Log.HasLoggedErrors; + } + + private bool IsTargetPathIncluded(ITaskItem item) + { + return TargetFilePrefixes + ?.Any(prefix => item.GetMetadata("TargetPath")?.StartsWith(prefix) == true) ?? true; + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/FileUtilities.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/FileUtilities.cs new file mode 100644 index 00000000000..97ab8b182c8 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/FileUtilities.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + internal static partial class FileUtilities + { + private static readonly HashSet s_assemblyExtensions = new HashSet( + new[] { ".dll", ".exe" }, + StringComparer.OrdinalIgnoreCase); + + public static Version GetFileVersion(string sourcePath) + { + var fvi = FileVersionInfo.GetVersionInfo(sourcePath); + + if (fvi != null) + { + return new Version(fvi.FileMajorPart, fvi.FileMinorPart, fvi.FileBuildPart, fvi.FilePrivatePart); + } + + return null; + } + + public static AssemblyName GetAssemblyName(string path) + { + if (!s_assemblyExtensions.Contains(Path.GetExtension(path))) + { + return null; + } + + try + { + using (var stream = File.OpenRead(path)) + using (var peReader = new PEReader(stream)) + { + if (peReader.HasMetadata) + { + return peReader.GetMetadataReader().GetAssemblyDefinition().GetAssemblyName(); + } + } + } + catch (BadImageFormatException) + { + // Not a valid assembly. + return null; + } + return null; + } + } + + internal struct AssemblyInformation + { + public string SimpleName { get; set; } + + public Version Version { get; set; } + + public string PublicKeyToken { get; set; } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromFileList.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromFileList.cs new file mode 100644 index 00000000000..cef7ec65e66 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromFileList.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.DotNet.Build.Tasks; +using Microsoft.Extensions.DependencyModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class GeneratePlatformManifestEntriesFromFileList : BuildTask + { + [Required] + public ITaskItem[] Files { get; set; } + + [Output] + public ITaskItem[] PlatformManifestEntries { get; set; } + + public override bool Execute() + { + var entries = new List(); + foreach (var file in Files) + { + entries.Add(new PlatformManifestEntry + { + Name = file.ItemSpec, + AssemblyVersion = FileUtilities.GetAssemblyName(file.GetMetadata("OriginalFilePath"))?.Version.ToString() ?? string.Empty, + FileVersion = FileUtilities.GetAssemblyName(file.GetMetadata("OriginalFilePath"))?.Version.ToString() ?? string.Empty + }); + } + + PlatformManifestEntries = entries.Select(entry => + { + var item = new TaskItem(entry.Name); + item.SetMetadata("AssemblyVersion", entry.AssemblyVersion); + item.SetMetadata("FileVersion", entry.FileVersion); + return item; + }).ToArray(); + return true; + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromTemplate.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromTemplate.cs new file mode 100644 index 00000000000..e7ef610602c --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GeneratePlatformManifestEntriesFromTemplate.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.DotNet.Build.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class GeneratePlatformManifestEntriesFromTemplate : BuildTask + { + [Required] + public ITaskItem[] PlatformManifestEntryTemplates { get; set; } + + [Required] + public ITaskItem[] Files { get; set; } + + public string FallbackAssemblyVersion { get; set; } + + public string FallbackFileVersion { get; set; } + + [Output] + public ITaskItem[] PlatformManifestEntries { get; set; } + + public override bool Execute() + { + List entries = new List(); + var files = Files.ToDictionary(file => Path.GetFileName(file.ItemSpec)); + foreach (var entryTemplate in PlatformManifestEntryTemplates) + { + if (files.TryGetValue(entryTemplate.ItemSpec, out ITaskItem existingFile)) + { + // This file in the platform manifest template exists on this platform. + // Use the information from the file itself in its entry. + entries.Add(new PlatformManifestEntry + { + Name = entryTemplate.ItemSpec, + AssemblyVersion = FileUtilities.GetAssemblyName(existingFile.ItemSpec)?.Version.ToString() ?? string.Empty, + FileVersion = FileUtilities.GetAssemblyName(existingFile.ItemSpec)?.Version.ToString() ?? string.Empty + }); + } + else + { + // This file does not exist on this platform. It only exists on another platform. + var isNativeEntry = entryTemplate.GetMetadata("IsNative") == "true"; + var entryTemplateExtension = Path.GetExtension(entryTemplate.ItemSpec); + // Use file version 0.0.0.0 for non-Windows executable and shared library files + bool useFileVersionZero = isNativeEntry && entryTemplateExtension != ".dll" && entryTemplateExtension != ".exe"; + string assemblyVersion = string.Empty; + string fileVersion = string.Empty; + if (isNativeEntry) + { + assemblyVersion = "0.0.0.0"; + } + else + { + string localAssemblyVersionFallback = entryTemplate.GetMetadata("FallbackAssemblyVersion"); + assemblyVersion = !string.IsNullOrEmpty(localAssemblyVersionFallback) ? localAssemblyVersionFallback : FallbackAssemblyVersion; + } + if (useFileVersionZero) + { + fileVersion = "0.0.0.0"; + } + else + { + string localFileVersionFallback = entryTemplate.GetMetadata("FallbackFileVersion"); + fileVersion = !string.IsNullOrEmpty(localFileVersionFallback) ? localFileVersionFallback : FallbackFileVersion; + } + entries.Add(new PlatformManifestEntry + { + Name = entryTemplate.ItemSpec, + AssemblyVersion = assemblyVersion, + FileVersion = fileVersion + }); + } + } + + PlatformManifestEntries = entries.Select(entry => + { + var item = new TaskItem(entry.Name); + if (string.IsNullOrEmpty(entry.AssemblyVersion)) + { + Log.LogError($"The platform manifest entry for '{entry.Name}' does not have a fallback assembly version specified and is not built on this platform."); + } + item.SetMetadata("AssemblyVersion", entry.AssemblyVersion); + if (string.IsNullOrEmpty(entry.FileVersion)) + { + Log.LogError($"The platform manifest entry for '{entry.Name}' does not have a fallback file version specified and is not built on this platform."); + } + item.SetMetadata("FileVersion", entry.FileVersion); + return item; + }).ToArray(); + return !Log.HasLoggedErrors; + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/GenerateSharedFrameworkDepsFile.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GenerateSharedFrameworkDepsFile.cs new file mode 100644 index 00000000000..bbb38d2b1c1 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/GenerateSharedFrameworkDepsFile.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.DotNet.Build.Tasks; +using Microsoft.Extensions.DependencyModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class GenerateSharedFrameworkDepsFile : BuildTask + { + [Required] + public string TargetFrameworkMoniker { get; set; } + + [Required] + public string RuntimeIdentifier { get; set; } + + [Required] + public string SharedFrameworkName { get; set; } + + [Required] + public string SharedFrameworkPackName { get; set; } + + [Required] + public string Version { get; set; } + + [Required] + public ITaskItem[] Files { get; set; } + + [Required] + public string IntermediateOutputPath { get; set; } + + public string SharedFrameworkDepsNameOverride { get; set; } + + [Output] + public ITaskItem GeneratedDepsFile { get; set; } + + public override bool Execute() + { + var target = new TargetInfo(TargetFrameworkMoniker, RuntimeIdentifier, string.Empty, isPortable: false); + var runtimeFiles = new List(); + var nativeFiles = new List(); + var resourceAssemblies = new List(); + + foreach (var file in Files) + { + if (!string.IsNullOrEmpty(file.GetMetadata("GeneratedBuildFile"))) + { + continue; + } + string filePath = file.ItemSpec; + string fileName = Path.GetFileName(filePath); + string fileVersion = FileUtilities.GetFileVersion(filePath)?.ToString() ?? string.Empty; + Version assemblyVersion = FileUtilities.GetAssemblyName(filePath)?.Version; + string cultureMaybe = file.GetMetadata("Culture"); + if (!string.IsNullOrEmpty(cultureMaybe)) + { + resourceAssemblies.Add(new ResourceAssembly(Path.Combine(cultureMaybe, fileName), cultureMaybe)); + } + else if (assemblyVersion == null) + { + var nativeFile = new RuntimeFile(fileName, null, fileVersion); + nativeFiles.Add(nativeFile); + } + else + { + var runtimeFile = new RuntimeFile(fileName, + fileVersion: fileVersion, + assemblyVersion: assemblyVersion.ToString()); + runtimeFiles.Add(runtimeFile); + } + } + + var runtimeLibrary = new RuntimeLibrary("package", + SharedFrameworkPackName, + Version, + hash: string.Empty, + runtimeAssemblyGroups: new[] { new RuntimeAssetGroup(string.Empty, runtimeFiles) }, + nativeLibraryGroups: new[] { new RuntimeAssetGroup(string.Empty, nativeFiles) }, + resourceAssemblies, + Array.Empty(), + hashPath: null, + path: $"{SharedFrameworkPackName.ToLowerInvariant()}/{Version}", + serviceable: true); + + var context = new DependencyContext(target, + CompilationOptions.Default, + Enumerable.Empty(), + new[] { runtimeLibrary }, + Enumerable.Empty()); + + var depsFileName = string.IsNullOrEmpty(SharedFrameworkDepsNameOverride) ? $"{SharedFrameworkName}.deps.json" : $"{SharedFrameworkDepsNameOverride}.deps.json"; + + var depsFilePath = Path.Combine(IntermediateOutputPath, depsFileName); + try + { + using var depsStream = File.Create(depsFilePath); + new DependencyContextWriter().Write(context, depsStream); + GeneratedDepsFile = new TaskItem(depsFilePath); + } + catch (Exception ex) + { + // If there is a problem, ensure we don't write a partially complete version to disk. + if (File.Exists(depsFilePath)) + { + File.Delete(depsFilePath); + } + Log.LogErrorFromException(ex, false); + return false; + } + return true; + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/Packaging/Extensions.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/Packaging/Extensions.cs new file mode 100644 index 00000000000..f8da82b1198 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/Packaging/Extensions.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Framework; +using NuGet; +using NuGet.Frameworks; +using NuGet.Versioning; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Versioning; + +// This file implements a subset of the Extensions class in the +// Microsoft.DotNet.Build.Tasks.Packaging project to support +// the Shared Framework SDK's usage of the VerifyClosure and +// VerifyTypes tasks used in shared framework validation. + +namespace Microsoft.DotNet.Build.Tasks.Packaging +{ + public static class Extensions + { + public static IEnumerable NullAsEmpty(this IEnumerable source) + { + if (source == null) + { + return Enumerable.Empty(); + } + + return source; + } + } +} + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/PlatformManifestEntry.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/PlatformManifestEntry.cs new file mode 100644 index 00000000000..55843662e73 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/PlatformManifestEntry.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + struct PlatformManifestEntry + { + public string Name { get; set; } + public string AssemblyVersion { get; set; } + public string FileVersion { get; set; } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/src/ValidateFileVersions.cs b/src/Microsoft.DotNet.SharedFramework.Sdk/src/ValidateFileVersions.cs new file mode 100644 index 00000000000..7942a4bb7be --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/src/ValidateFileVersions.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Build.Construction; +using Microsoft.Build.Framework; +using Microsoft.DotNet.Build.Tasks; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.SharedFramework.Sdk +{ + public class ValidateFileVersions : BuildTask + { + private static readonly Version ZeroVersion = new Version(0, 0, 0, 0); + + [Required] + public ITaskItem[] Files { get; set; } + + public override bool Execute() + { + var fileVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach(var file in Files) + { + if (file.GetMetadata("IsSymbolFile").Equals("true", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var fileName = Path.GetFileName(file.ItemSpec); + + var current = GetFileVersionData(file); + + FileVersionData existing; + + if (fileVersions.TryGetValue(fileName, out existing)) + { + if (current.AssemblyVersion != null) + { + if (existing.AssemblyVersion == null) + { + fileVersions[fileName] = current; + continue; + } + else if (current.AssemblyVersion != existing.AssemblyVersion) + { + if (current.AssemblyVersion > existing.AssemblyVersion) + { + fileVersions[fileName] = current; + } + continue; + } + } + + if (current.FileVersion != null && + existing.FileVersion != null) + { + if (current.FileVersion > existing.FileVersion) + { + fileVersions[fileName] = current; + } + } + } + else + { + fileVersions[fileName] = current; + } + } + + // Check for versionless files after all duplicate filenames are resolved, rather than + // logging errors immediately upon encountering a versionless file. There may be + // duplicate filenames where only one has a version, and this is ok. The highest version + // is used. + var versionlessFiles = fileVersions + .Where(p => + p.Key.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) || + p.Key.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + .Where(p => (p.Value.FileVersion ?? ZeroVersion) == ZeroVersion) + .Select(p => p.Value.File.ItemSpec) + .ToArray(); + + if (versionlessFiles.Any()) + { + Log.LogError( + $"Missing FileVersion in {versionlessFiles.Length} shared framework files:" + + string.Concat(versionlessFiles.Select(f => Environment.NewLine + f))); + } + + return !Log.HasLoggedErrors; + } + + FileVersionData GetFileVersionData(ITaskItem file) + { + var filePath = file.GetMetadata("FullPath"); + + if (File.Exists(filePath)) + { + return new FileVersionData() + { + AssemblyVersion = FileUtilities.GetAssemblyName(filePath)?.Version, + FileVersion = FileUtilities.GetFileVersion(filePath), + File = file + }; + } + else + { + // allow for the item to specify version directly + Version assemblyVersion, fileVersion; + + Version.TryParse(file.GetMetadata("AssemblyVersion"), out assemblyVersion); + Version.TryParse(file.GetMetadata("FileVersion"), out fileVersion); + + if (fileVersion == null) + { + // FileVersionInfo will return 0.0.0.0 if a file doesn't have a version. + // match that behavior + fileVersion = ZeroVersion; + } + + return new FileVersionData() + { + AssemblyVersion = assemblyVersion, + FileVersion = fileVersion, + File = file + }; + } + } + + class FileVersionData + { + public Version AssemblyVersion { get; set; } + public Version FileVersion { get; set; } + public ITaskItem File { get; set; } + } + } +} diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.MultiRid.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.MultiRid.targets new file mode 100644 index 00000000000..64ba4c0acc8 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.MultiRid.targets @@ -0,0 +1,92 @@ + + + + + + + + <_RuntimeIdentifiers Include="$(RuntimeIdentifiers)" /> + + <_RuntimeIdentifiers Include="@(_RuntimeIdentifiers->Trim()->Distinct())" /> + <_InnerBuildProjects Include="$(MSBuildProjectFile)"> + RuntimeIdentifier=%(_RuntimeIdentifiers.Identity) + + + + + + + + + + + + + + + Build + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.props b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.props new file mode 100644 index 00000000000..94f95dce251 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.props @@ -0,0 +1,3 @@ + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.targets new file mode 100644 index 00000000000..bb6f43f9004 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/Microsoft.DotNet.SharedFramework.Sdk.targets @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + %(CurrentProjectServicingConfiguration.PatchVersion) + $(MostRecentProducedServicingPatchVersion) + + + + + + + true + + + + <_TargetRuntimeIdentifiers Include="$(RuntimeIdentifiers)" /> + + + + + <_RidInRidList Condition="'%(_TargetRuntimeIdentifiers.Identity)' == '$(RuntimeIdentifier)'">true + true + + + + + + + + + + <_PackageTypeRequiresRid Condition="'$(PlatformPackageType)' == 'RuntimePack' or '$(PlatformPackageType)' == 'AppHostPack'">true + + + + $(OverridePackageId) + + + + $(SharedFrameworkName).Ref + Targeting Pack + pack.targeting + + + $(SharedFrameworkName).Runtime.$(RuntimeIdentifier) + Runtime + sharedframework.$(SharedFrameworkName) + + + $(SharedFrameworkName).Host.$(RuntimeIdentifier) + AppHost Pack + pack.apphost + + + $(SharedFrameworkName) + pack.$(PlatformPackageType.ToLower()) + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.props b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.props new file mode 100644 index 00000000000..30d47fc7fdb --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.props @@ -0,0 +1,19 @@ + + + $(RepoRoot)LICENSE.TXT + + ResolveFrameworkReferences; + ResolveReferences; + + true + false + true + true + true + $(NoWarn);NU5128;NU5131 + DotnetPlatform + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets new file mode 100644 index 00000000000..6018e76a05a --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/sharedfx.targets @@ -0,0 +1,581 @@ + + + $([System.IO.Path]::GetFileName('$(LicenseFile)')) + true + true + true + true + $(AllowedOutputExtensionsInSymbolsPackageBuildOutputFolder);.map;.dbg;.debug;.dwarf + runtimes/$(RuntimeIdentifier)/lib/$(TargetFramework) + + + + + + + true + + + + + + + true + + + + + + + <_SymbolFilesToPackage> + true + + + + + + <_SymbolFilesToPackage Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(FileName).pdb')" Exclude="@(RuntimePackAsset)" /> + <_SymbolFilesToPackage Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(FileName)%(Extension).dbg')" Exclude="@(RuntimePackAsset)" /> + <_SymbolFilesToPackage Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(FileName)%(Extension).dwarf')" Exclude="@(RuntimePackAsset)" /> + <_SymbolFilesToPackage Remove="@(_SymbolFilesToPackage)" Condition="!Exists('%(Identity)')" /> + + + + + + + + + + + + + + + + + + + + + <_r2rSymbolFiles Include="@(ResolvedFileToPublish->'%(RootDir)%(Directory)%(FileName).ni.pdb')" /> + + + + <_r2rSymbolFilePattern>@(ResolvedFileToPublish->'%(RootDir)%(Directory)%(FileName).ni.*.map') + + + + + + + + + + + + + + + + runtimes/$(RuntimeIdentifier)/lib/$(TargetFramework) + runtimes/$(RuntimeIdentifier)/native + + + %(FilesToPackage.TargetPath)/%(FilesToPackage.DestinationSubDirectory) + + + + + + ref/$(TargetFramework) + + + + + + + ref/$(TargetFramework)/%(RecursiveDir) + + + + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + <_FilesForDepsFile Include="@(FilesToPackage)" + Condition="'%(FilesToPackage.IsSymbolFile)' != 'true' and + '%(FilesToPackage.PackOnly)' != 'true' and + '%(FilesToPackage.ExcludeFromDataFiles)' != 'true' and + $([System.String]::new('%(FilesToPackage.TargetPath)').StartsWith('runtimes/$(RuntimeIdentifier)'))" /> + + + + + + <_SharedFrameworkDepsFile> + $(HostJsonTargetPath) + true + + + + + + + + + true + + $(IntermediateOutputPath)$(SharedFrameworkName).runtimeconfig.json + $(IntermediateOutputPath)$(SharedFrameworkHostFileNameOverride).runtimeconfig.json + + + + + + + + + + + + + <_PackagedFileNames Include="@(FilesToPackage->'%(Filename)%(Extension)')" + Condition="'%(FilesToPackage.IsSymbolFile)' != 'true' and + '%(FilesToPackage.GeneratedBuildFile)' != 'true' and + '%(FilesToPackage.ExcludeFromDataFiles)' != 'true' and + $([System.String]::new('%(FilesToPackage.TargetPath)').StartsWith('runtimes/'))" + OriginalFilePath="$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '%(Identity)'))" /> + + + + + + + + + + + $(FileVersion) + $(AssemblyVersion) + + + + + <_FilesMissingInManifestEntries Include="@(_PackagedFileNamesFiltered)" Exclude="@(PlatformManifestFileEntry)" /> + + + + + + + + + + + + + + + + + + + <_RuntimePackHarvestProperties Condition="'$(RuntimePackPlatformForManifest)' != ''">RuntimeIdentifier=$(RuntimePackPlatformForManifest) + <_RuntimePackBuildTarget>_GetPackagedFileNamesNoDuplicates + + + + + + + + + + <_RuntimePackHarvestProperties>$(_RuntimePackHarvestProperties);InnerTargets=$(_RuntimePackBuildTarget) + <_RuntimePackBuildTarget>Build + + + + + + + + + + + + + + + + + + <_PlatformManifestFile Include="$(IntermediateOutputPath)PlatformManifest.txt" TargetPath="data" GeneratedBuildFile="true" /> + + + + + + + + + + + + + + <_FrameworkRuntimeList Include="$(IntermediateOutputPath)FrameworkList.xml" + Condition="'$(PlatformPackageType)' == 'TargetingPack'" /> + <_FrameworkRuntimeList Include="$(IntermediateOutputPath)RuntimeList.xml" + Condition="'$(PlatformPackageType)' == 'RuntimePack'" /> + <_FrameworkRuntimeList> + data + + + <_FrameworkListRootAttribute Include="TargetFrameworkIdentifier" Value="$(TargetFrameworkIdentifier)" /> + <_FrameworkListRootAttribute Include="TargetFrameworkVersion" Value="$(TargetFrameworkVersion.TrimStart('vV'))" /> + <_FrameworkListRootAttribute Include="FrameworkName" Value="$(SharedFrameworkName)" /> + <_FrameworkListRootAttribute Include="Name" Value="$(SharedFrameworkFriendlyName)" /> + <_FrameworkListTargetFilePrefix Include="ref/;runtimes/" /> + + + + + + + + + + + + + data + + + + + + + + <_VersionsFile Include="$(IntermediateOutputPath)$(SharedFrameworkName).versions.txt" TargetPath="" GeneratedBuildFile="true" /> + + + + + + + + + + + + <_closureFiles + Include="@(FilesToPackage)" + Condition="('%(Extension)' == '.dll' or '%(Extension)' == '$(LibraryFileExtension)') and '%(FilesToPackage.Culture)' == ''" /> + <_closureFileNames Include="@(_closureFiles->'%(FileName)')" Original="%(Identity)" /> + + <_closureFileNamesFiltered Include="@(_closureFileNames)" Exclude="@(ExcludeFromClosure)"/> + <_closureFileFiltered Include="@(_closureFileNamesFiltered->'%(Original)')"/> + + + + + + + + + + + + + + + + + + + <_dupTypeFiles + Include="@(FilesToPackage)" + Condition="('%(Extension)' == '.dll' or '%(Extension)' == '$(LibraryFileExtension)') and '%(FilesToPackage.Culture)' == ''" /> + <_dupTypeFileName Include="@(_dupTypeFiles->'%(FileName)')" Original="%(Identity)" /> + + <_dupTypeFileNamesFiltered Include="@(_dupTypeFileName)" Exclude="@(ExcludeFromDuplicateTypes)"/> + <_dupTypeFileFiltered Include="@(_dupTypeFileNamesFiltered->'%(Original)')"/> + + + + + + + + + + <_filesToVerify Include="@(FilesToPackage)" Condition="'%(Extension)' == '.dll' or '%(Extension)' == '.exe'" /> + + + + + + + + + + + + + + + runtimes/$(RuntimeIdentifier)/lib + true + + + <_PackageFiles Include="@(_FilesToPackage)" PackagePath="%(_FilesToPackage.TargetPath)" Condition="'%(_FilesToPackage.IsSymbolFile)' != 'true'" /> + + <_TargetPathsToSymbols Include="@(_FilesToPackage)" + Condition="'%(_FilesToPackage.IsSymbolFile)' == 'true'" + TargetFramework="$(TargetFramework)" + TargetPath="$([MSBuild]::MakeRelative($([MSBuild]::NormalizePath('$(BuildOutputTargetFolder)/$(TargetFramework)')), $([MSBuild]::NormalizePath('%(_FilesToPackage.TargetPath)', '%(Filename)%(Extension)'))))"/> + + + + + + + + + + + + + + + <_PackagedFilesToPublish Include="@(_FilesToPackage)" Condition="'%(_FilesToPackage.PackOnly)' != 'true'" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(BuildDependsOn);_GetSkipBuildProps;_CreateNuGetPackage + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/product/sharedfxdir.wxs b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/product/sharedfxdir.wxs new file mode 100644 index 00000000000..49c02a53ee1 --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/product/sharedfxdir.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/wix.targets b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/wix.targets new file mode 100644 index 00000000000..1142b02a14d --- /dev/null +++ b/src/Microsoft.DotNet.SharedFramework.Sdk/targets/windows/wix.targets @@ -0,0 +1,31 @@ + + + + true + true + + + + + + + + + + + + + sharedfx\$(SharedFrameworkName) + + + + + +