Add pkgproj-based DEB/RPM build for targeting packs (dotnet/core-setup#5369)
authorDavis Goodin <dagood@users.noreply.github.com>
Tue, 12 Mar 2019 21:24:49 +0000 (16:24 -0500)
committerGitHub <noreply@github.com>
Tue, 12 Mar 2019 21:24:49 +0000 (16:24 -0500)
* Pkgproj-based DEB/RPM build for targeting packs

* Code review fixes

Make clean displayName clearer.

Add '-a' to 'docker system prune'.

Fix comments for fpm and debuild tool checks.

* Fix bad indent

Commit migrated from https://github.com/dotnet/core-setup/commit/f6441d4af6030bd40eb0440038bbe4d522d266cf

27 files changed:
eng/jobs/bash-build.yml
eng/jobs/finalize-publish.yml
eng/jobs/osx-build.yml
eng/jobs/steps/build-linux-package.yml [new file with mode: 0644]
eng/jobs/windows-build.yml
eng/pipelines/installer/azure-pipelines.yml
src/installer/pkg/deps/dotnet-deb-tool-consumer.csproj [moved from src/installer/pkg/packaging/deb/dotnet-deb-tool-consumer.csproj with 100% similarity]
src/installer/pkg/deps/init-deb-tool-consumer.proj [new file with mode: 0644]
src/installer/pkg/dir.props
src/installer/pkg/dir.targets
src/installer/pkg/dir.traversal.targets
src/installer/pkg/packaging/deb/package.props
src/installer/pkg/packaging/deb/package.targets
src/installer/pkg/packaging/dir.proj
src/installer/pkg/packaging/dir.props
src/installer/pkg/packaging/rpm/package.props
src/installer/pkg/packaging/rpm/package.targets
src/installer/pkg/projects/dir.props
src/installer/pkg/projects/dir.targets
src/installer/pkg/projects/dir.traversal.targets
src/installer/pkg/projects/framework.packaging.targets
src/installer/pkg/projects/installer.builds [new file with mode: 0644]
src/installer/pkg/projects/netcoreapp/pkg/dir.props
src/installer/pkg/projects/windowsdesktop/pkg/dir.props
tools-local/scripts/dev-build-deb-docker.sh [new file with mode: 0755]
tools-local/tasks/BuildFPMToolPreReqs.cs
tools-local/tasks/GenerateJsonObjectString.cs [new file with mode: 0644]

index 3a4c6c9..9fff60b 100644 (file)
@@ -6,8 +6,7 @@ parameters:
   displayName: null
   dockerImage: null
   osGroup: Linux
-  packageDistroListDeb: null
-  packageDistroListRpm: null
+  packageDistroList: null
   skipTests: $(SkipTests)
   strategy:
     matrix: 
@@ -52,6 +51,7 @@ jobs:
         -TargetArchitecture=${{ parameters.targetArchitecture }}
         -- /p:StabilizePackageVersion=$(IsStable)
         /nr:false
+        /bl:msbuild.binlog
         ${{ parameters.additionalMSBuildArgs }}
 
       PublishArguments: /p:PublishType=$(_PublishType)
@@ -61,10 +61,10 @@ jobs:
         /p:StabilizePackageVersion=$(IsStable)
         /p:TargetArchitecture=${{ parameters.targetArchitecture }}
         /nr:false
+        /bl:msbuild.publish.binlog
          ${{ parameters.additionalMSBuildArgs }}
 
-      CommonMSBuildArgs: /bl
-        /p:ConfigurationGroup=$(_BuildConfig)
+      CommonMSBuildArgs: /p:ConfigurationGroup=$(_BuildConfig)
         /p:OfficialBuildId=$(OfficialBuildId)
         /p:OSGroup=${{ parameters.osGroup }}
         /p:PortableBuild=false
@@ -72,32 +72,17 @@ jobs:
         /p:TargetArchitecture=${{ parameters.targetArchitecture }}
         /nr:false
 
-      # Packaging related variables
-      # Debian
-      CommonDebianRunCommand: run 
-        -v $(Build.SourcesDirectory):/root/coresetup
-        -v $(Build.StagingDirectory)/sharedFrameworkPublish/:/root/sharedFrameworkPublish/
-        -w=/root/coresetup
-        mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-14.04-debpkg-e5cf912-20175003025046
-        /root/coresetup/Tools/msbuild.sh
-      DebianPackagingCommand: $(CommonDebianRunCommand)
-        /root/coresetup/src/pkg/packaging/dir.proj
-        /p:UsePrebuiltPortableBinariesForInstallers=true
+      # Tell the build to package up the bits from the portable build.
+      PackagePortableBitsArgs: >-
         /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/
-        $(CommonMSBuildArgs)
+        /p:InstallerSourceOSPlatformConfig=linux-x64.$(_BuildConfig)
 
-      # RPM
-      CommonRpmRunCommand: run
+      MSBuildScript: /root/coresetup/Tools/msbuild.sh
+      DockerRunMSBuild: >-
+        docker run
         -v $(Build.SourcesDirectory):/root/coresetup
         -v $(Build.StagingDirectory)/sharedFrameworkPublish/:/root/sharedFrameworkPublish/
         -w=/root/coresetup
-        mcr.microsoft.com/dotnet-buildtools/prereqs:rhel-7-rpmpkg-c982313-20174116044113
-        /root/coresetup/Tools/msbuild.sh
-      RpmPackagingCommand: $(CommonRpmRunCommand)
-        /root/coresetup/src/pkg/packaging/dir.proj
-        /p:UsePrebuiltPortableBinariesForInstallers=true
-        /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/
-        $(CommonMSBuildArgs)
 
       # Disable MSBuild node reuse in case this build is running on a persistent agent.
       # Use environment variable rather than /nr:false to make sure internal Execs that run MSBuild
@@ -105,30 +90,39 @@ jobs:
       MSBUILDDISABLENODEREUSE: 1
     steps:
 
-    - script: df -h
-      displayName: Check space (df -h)
-      continueOnError: true
+    # Builds don't set user ID, so files might be owned by root and unable to be cleaned up by AzDO.
+    # Clean up the build dirs ourselves in another Docker container to avoid failures.
+    # Using hosted agents is tracked by https://github.com/dotnet/core-setup/issues/4997
+    - script: |
+        set -x
+        docker run --rm \
+          -v "$(Agent.BuildDirectory):/root/build" \
+          -w /root/build \
+          ${{ parameters.dockerImage }} \
+          bash -c '
+            rm -v -rf a b
+            cd s
+            git clean -xdf'
+      displayName: Clean up old artifacts owned by root
 
     # Build binary and nuget packages
-    - script: $(RunArguments)
-        ./build.sh
-        $(BuildArguments)
+    - script: |
+        set -x
+        df -h
+        $(RunArguments) ./build.sh $(BuildArguments)
       displayName: Build
-      workingDirectory: '$(Build.SourcesDirectory)'
-
-    - script: df -h
-      displayName: Check space (df -h)
-      continueOnError: true
 
     # Publish only if internal and not PR 
     - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
-      - script: $(RunArguments)
-          ./Tools/msbuild.sh
-          ./publish/publish.proj
-          $(PublishArguments)
-          $(_CommonPublishArgs)
+      - script: |
+          set -x
+          df -h
+          $(RunArguments) \
+            ./Tools/msbuild.sh \
+            ./publish/publish.proj \
+            $(PublishArguments) \
+            $(_CommonPublishArgs)
         displayName: Publish
-        workingDirectory: '$(Build.SourcesDirectory)'
         condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
 
     # Only for glibc leg, here we produce RPMs and Debs
@@ -138,121 +132,30 @@ jobs:
         inputs:
           SourceFolder: '$(Build.SourcesDirectory)/bin/obj/linux-x64.$(_BuildConfig)/sharedFrameworkPublish'
           TargetFolder: '$(Build.StagingDirectory)/sharedFrameworkPublish'
-      - script: df -h
-        displayName: Check space (df -h)
-        continueOnError: true
-      - script: docker 
-          $(CommonDebianRunCommand)
-          /root/coresetup/build.proj
-          /t:BuildTraversalBuildDependencies
-          $(CommonMSBuildArgs)
-        displayName: Build traversal build dependencies - Ubuntu 14.04
-        workingDirectory: '$(Build.SourcesDirectory)'
-      - script: df -h
-        displayName: Check space (df -h)
-        continueOnError: true
-
-      # Debian packaging
-      - script: docker 
-          $(DebianPackagingCommand)
-        displayName: 'Package Runtime packages and Runtime Dep - Ubuntu 14.04'
-        workingDirectory: '$(Build.SourcesDirectory)'
-      - script: df -h
-        displayName: Check space (df -h)
-        continueOnError: true
-
-      - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
-        - script: docker
-            $(CommonDebianRunCommand)
-            /root/coresetup/publish/publish.proj
-            /p:PublishType=$(_PublishType)
-            /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/
-            $(CommonMSBuildArgs) 
-            $(_CommonPublishArgs)
-          displayName: 'Publish Runtime and Runtime Dep - Ubuntu 14.04'
-          workingDirectory: '$(Build.SourcesDirectory)'
-          condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
-        - script: df -h
-          displayName: Check space (df -h)
-          continueOnError: true
-
-      # Package and publish for each debian distros
-      - ${{ each debdistrorid in parameters.packageDistroListDeb }}:
-        - script: docker
-            $(DebianPackagingCommand) /p:BuildRuntimeDebs=false
-            /p:OutputRid=${{ debdistrorid }}-${{ parameters.targetArchitecture }}
-          displayName: 'Package Runtime Dep - ${{ debdistrorid }}'
-        - script: df -h
-          displayName: Check space (df -h)
-          continueOnError: true
-        - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
-          - script: docker
-              $(CommonDebianRunCommand)
-              /root/coresetup/publish/publish.proj
-              /p:PublishType=$(_PublishType)
-              /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/ 
-              $(CommonMSBuildArgs)
-              $(_CommonPublishArgs)
-              /p:OutputRid=${{ debdistrorid }}-${{ parameters.targetArchitecture }}
-            displayName: 'Publish Runtime Dep - ${{ debdistrorid }}'
-            workingDirectory: '$(Build.SourcesDirectory)'
-            condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
-          - script: df -h
-            displayName: Check space (df -h)
-            continueOnError: true
 
-      # RPM Packaging
-      - script: docker
-          $(CommonRpmRunCommand)
-          /root/coresetup/build.proj
-          /t:BuildTraversalBuildDependencies
-          $(CommonMSBuildArgs) 
-        displayName: Build traversal build dependencies - Rhel7
-        workingDirectory: '$(Build.SourcesDirectory)'
-      - script: docker 
-          $(RpmPackagingCommand)
-        displayName: Package Runtime Dep - Rhel7
-      - script: df -h
-        displayName: Check space (df -h)
-        continueOnError: true
-      - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
-        - script: docker 
-            $(CommonRpmRunCommand) 
-            /root/coresetup/publish/publish.proj
-            /p:PublishType=$(_PublishType)
-            /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/
-            $(CommonMSBuildArgs)
-            $(_CommonPublishArgs)
-          displayName: Publish Runtime Dep - Rhel7
-          workingDirectory: '$(Build.SourcesDirectory)'
-          condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
+      - ${{ each packageBuild in parameters.packageDistroList }}:
+        # This leg's RID matches the build image. Build its distro-dependent packages, as well as
+        # the distro-independent installers. (There's no particular reason to build the distro-
+        # independent installers on this leg, but we need to do it somewhere.)
+        - template: steps/build-linux-package.yml
+          parameters:
+            buildTraversalBuildDependencies: true
+            distroRid: ${{ packageBuild.imageRid }}
+            image: ${{ packageBuild.image }}
+            packageStepDescription: Runtime Deps, Runtime, Framework Packs installers
+
+        - ${{ each rid in packageBuild.rids }}:
+          # Build distro-dependent packages.
+          - template: steps/build-linux-package.yml
+            parameters:
+              distroRid: ${{ rid }}
+              image: ${{ packageBuild.image }}
+              outputRidArg: /p:OutputRid=${{ rid }}-${{ parameters.targetArchitecture }}
+              packageStepDescription: Runtime Deps installers
+              packagingArgs: /p:BuildDistroIndependentInstallers=false
 
-      # Package and publish for each rpm distros
-      - ${{ each rpmdistrorid in parameters.packageDistroListRpm }}: 
-        - script: docker 
-            $(RpmPackagingCommand) /p:BuildRuntimeRpms=false
-            /p:OutputRid=${{ rpmdistrorid }}-${{ parameters.targetArchitecture }}
-          displayName: 'Package Runtime Dep - ${{ rpmdistrorid }}'
-        - script: df -h
-          displayName: Check space (df -h)
-          continueOnError: true
-        - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
-          - script: docker
-              $(CommonRpmRunCommand)
-              /root/coresetup/publish/publish.proj
-              /p:PublishType=$(_PublishType)
-              /p:SharedFrameworkPublishDir=/root/sharedFrameworkPublish/
-              $(CommonMSBuildArgs)
-              $(_CommonPublishArgs)
-              /p:OutputRid=${{ rpmdistrorid }}-${{ parameters.targetArchitecture }}
-            displayName: 'Publish Runtime Dep -${{ rpmdistrorid }}'
-            workingDirectory: '$(Build.SourcesDirectory)'
-            condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
-          - script: df -h
-            displayName: Check space (df -h)
-            continueOnError: true
     - task: CopyFiles@2
-      displayName: Copy Files to $(Build.StagingDirectory)\BuildLogs
+      displayName: Copy logs to stage
       inputs:
         SourceFolder: '$(Build.SourcesDirectory)'
         Contents: |
@@ -261,6 +164,7 @@ jobs:
         TargetFolder: '$(Build.StagingDirectory)\BuildLogs'
       continueOnError: true
       condition: succeededOrFailed()
+
     - task: PublishBuildArtifacts@1
       displayName: Publish Artifact BuildLogs
       inputs:
@@ -269,6 +173,24 @@ jobs:
       continueOnError: true
       condition: succeededOrFailed()
 
+    - task: CopyFiles@2
+      displayName: Copy packages to stage
+      inputs:
+        SourceFolder: '$(Build.SourcesDirectory)/bin/'
+        Contents: |
+          */packages/*
+        TargetFolder: '$(Build.StagingDirectory)\Packages'
+      continueOnError: true
+      condition: succeededOrFailed()
+
+    - task: PublishBuildArtifacts@1
+      displayName: Publish Artifact Packages
+      inputs:
+        PathtoPublish: '$(Build.StagingDirectory)\Packages'
+        ArtifactName: ${{ parameters.displayName }}-$(_BuildConfig)-Packages
+      continueOnError: true
+      condition: succeededOrFailed()
+
     - script: df -h
       displayName: Check space (df -h)
       condition: always()
@@ -276,10 +198,6 @@ jobs:
 
     # Force clean up machine in case any docker images are left behind
     - ${{ if ne(parameters.displayName, 'Build_FreeBSD_x64')}}:
-      - script: docker system prune -f
+      - script: docker system prune -af && df -h
         displayName: Run Docker clean up
         condition: succeededOrFailed()
-      - script: df -h
-        displayName: Check space (df -h)
-        condition: always()
-        continueOnError: true
index ecd72bf..a956b04 100644 (file)
@@ -15,6 +15,8 @@ jobs:
         name: dotnet-internal-temp
     # Double the default timeout. Publishing is subject to huge delays due to contention on the dotnet-core blob feed
     timeoutInMinutes: 120
+    workspace:
+      clean: all
     variables:
       _PublishType: ${{ parameters._PublishType}}
 
index 8c1e506..5d46490 100644 (file)
@@ -13,6 +13,8 @@ jobs:
       release:
         _BuildConfig: Release
         _PublishType: blob
+  workspace:
+    clean: all
   steps:
   - script: $(Build.SourcesDirectory)/build.sh
       -OfficialBuildId=$(OfficialBuildId)
diff --git a/eng/jobs/steps/build-linux-package.yml b/eng/jobs/steps/build-linux-package.yml
new file mode 100644 (file)
index 0000000..ac81413
--- /dev/null
@@ -0,0 +1,48 @@
+parameters:
+  buildTraversalBuildDependencies: false
+  distroRid: null
+  image: null
+  outputRidArg: ''
+  packageStepDescription: null
+  packagingArgs: ''
+
+steps:
+- ${{ if eq(parameters.buildTraversalBuildDependencies, true) }}:
+  - script: |
+      set -x
+      df -h
+      $(DockerRunMSBuild) ${{ parameters.image }} $(MSBuildScript) \
+        /root/coresetup/build.proj \
+        /t:BuildTraversalBuildDependencies \
+        $(CommonMSBuildArgs) \
+        /bl:msbuild.${{ parameters.distroRid }}.traversaldependencies.binlog
+    displayName: ====== Build traversal build dependencies - ${{ parameters.distroRid }}
+
+- script: |
+    set -x
+    df -h
+    $(DockerRunMSBuild) ${{ parameters.image }} $(MSBuildScript) \
+      /root/coresetup/src/pkg/packaging/dir.proj \
+      /p:UsePrebuiltPortableBinariesForInstallers=true \
+      $(PackagePortableBitsArgs) \
+      /p:GenerateProjectInstallers=true \
+      ${{ parameters.packagingArgs }} \
+      $(CommonMSBuildArgs) \
+      ${{ parameters.outputRidArg }} \
+      /bl:msbuild.${{ parameters.distroRid }}.installers.binlog
+  displayName: Package ${{ parameters.packageStepDescription }} - ${{ parameters.distroRid }}
+
+- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
+  - script: |
+      set -x
+      df -h
+      $(DockerRunMSBuild) ${{ parameters.image }} $(MSBuildScript) \
+        /root/coresetup/publish/publish.proj \
+        /p:PublishType=$(_PublishType) \
+        $(PackagePortableBitsArgs) \
+        $(CommonMSBuildArgs) \
+        $(_CommonPublishArgs) \
+        ${{ parameters.outputRidArg }} \
+        /bl:msbuild.${{ parameters.distroRid }}.publish.binlog
+    displayName: -> Publish ${{ parameters.packageStepDescription }} - ${{ parameters.distroRid }}
+    condition: and(succeeded(), eq(variables._BuildConfig, 'Release'))
index 92a425a..332ebb5 100644 (file)
@@ -27,6 +27,8 @@ jobs:
         release:
           _BuildConfig: Release
           _PublishType: blob
+    workspace:
+      clean: all
     variables: 
       CommonMSBuildArgs: "/p:ConfigurationGroup=$(_BuildConfig) 
                           /p:TargetArchitecture=${{ parameters.targetArchitecture }} 
index 3808d70..906640a 100644 (file)
@@ -144,8 +144,22 @@ jobs:
   parameters:
     displayName: Build_Linux_x64_glibc
     dockerImage: mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-d485f41-20173404063424
-    packageDistroListDeb: [debian.8,debian.9,ubuntu.16.04,ubuntu.18.04]
-    packageDistroListRpm: [centos.7,fedora.27,opensuse.42,oraclelinux.7,sles.12]
+    packageDistroList:
+    - image: mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-14.04-debpkg-e5cf912-20175003025046
+      imageRid: ubuntu.14.04
+      rids:
+      - debian.8
+      - debian.9
+      - ubuntu.16.04
+      - ubuntu.18.04
+    - image: mcr.microsoft.com/dotnet-buildtools/prereqs:rhel-7-rpmpkg-c982313-20174116044113
+      imageRid: rhel.7
+      rids:
+      - centos.7
+      - fedora.27
+      - opensuse.42
+      - oraclelinux.7
+      - sles.12
     portableBuild: true
     targetArchitecture: x64
 
diff --git a/src/installer/pkg/deps/init-deb-tool-consumer.proj b/src/installer/pkg/deps/init-deb-tool-consumer.proj
new file mode 100644 (file)
index 0000000..e80d214
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+
+  <PropertyGroup>
+    <DotnetDebToolSourceDir>$(ProjectDir)tools-local/setuptools/dotnet-deb-tool/</DotnetDebToolSourceDir>
+    <InitSemaphore>$(DebtoolConsumerDeployDir)copied.sem</InitSemaphore>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <DebToolFile Include="$(DotnetDebToolSourceDir)**/*.*" />
+  </ItemGroup>
+
+  <!-- Create deb tool package and set up the project that consumes it as a CLI tool. -->
+  <Target Name="Build"
+          Inputs="@(DebToolFile)"
+          Outputs="$(InitSemaphore)">
+    <PropertyGroup>
+      <VersionSuffixArg Condition="'$(VersionSuffix)' != ''">--version-suffix $(VersionSuffix)</VersionSuffixArg>
+    </PropertyGroup>
+
+    <Exec Command="$(DotnetRestoreCommand) $(DotnetDebToolSourceDir)"/>
+    <Exec Command="$(DotnetToolCommand) pack $(DotnetDebToolSourceDir) --output $(PackageOutputPath) $(VersionSuffixArg)"/>
+
+    <RemoveDir
+      Condition="Exists('$(DebtoolConsumerDeployDir)')"
+      Directories="$(DebtoolConsumerDeployDir)" />
+
+    <Copy
+      SourceFiles="$(DebtoolConsumerProjectFile)"
+      DestinationFolder="$(DebtoolConsumerDeployDir)" />
+
+    <Exec
+      Command="$(DotnetRestoreCommand) -s $(PackageOutputPath)"
+      WorkingDirectory="$(DebtoolConsumerDeployDir)" />
+
+    <Touch Files="$(InitSemaphore)" AlwaysCreate="true" />
+  </Target>
+
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
index b83e911..ce85d6c 100644 (file)
     <SkipIndexCheck>true</SkipIndexCheck>
   </PropertyGroup>
 
+  <PropertyGroup>
+    <PackagesIntermediateDir>$(IntermediateOutputRootPath)packages/</PackagesIntermediateDir>
+
+    <RpmTemplatesDir>$(MSBuildThisFileDirectory)packaging/rpm/templates/</RpmTemplatesDir>
+
+    <DebtoolConsumerProjectName>dotnet-deb-tool-consumer.csproj</DebtoolConsumerProjectName>
+    <DebtoolConsumerProjectFile>$(MSBuildThisFileDirectory)deps/$(DebtoolConsumerProjectName)</DebtoolConsumerProjectFile>
+    <!--
+      Intermediate dir the debtool project is copied to. Use this as the working directory for
+      "dotnet deb-tool" commands, after depending on InitializeDotnetDebTool target.
+    -->
+    <DebtoolConsumerDeployDir>$(IntermediateOutputRootPath)$(DebtoolConsumerProjectName)/</DebtoolConsumerDeployDir>
+
+    <IsDebianBasedDistro
+      Condition="
+        $(PackageTargetRid.StartsWith('ubuntu')) or
+        $(PackageTargetRid.StartsWith('debian')) or 
+        $(PackageTargetRid.StartsWith('linuxmint'))">true</IsDebianBasedDistro>
+    <BuildDebPackage Condition="'$(IsDebianBasedDistro)' == true and '$(TargetArchitecture)' == 'x64'">true</BuildDebPackage>
+
+    <IsRPMBasedDistro Condition="'$(InstallerExtension)' == '.rpm'">true</IsRPMBasedDistro>
+    <BuildRpmPackage Condition="'$(IsRPMBasedDistro)' == true and '$(TargetArchitecture)' == 'x64'">true</BuildRpmPackage>
+
+    <!--
+      Some Debian and RPM packages only need to be built once for all supported distros, as the bits
+      are portable. By default, build these packages, but in the official build this can be turned
+      off to avoid producing redundant packages.
+    -->
+    <BuildDistroIndependentInstallers Condition="'$(BuildDistroIndependentInstallers)' == ''">true</BuildDistroIndependentInstallers>
+  </PropertyGroup>
+
   <ItemGroup>
     <PackageIndex Include="$(PackageIndexFile)" />
   </ItemGroup>
index 4411425..9df96a0 100644 (file)
@@ -1,7 +1,58 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="12.0" InitialTargets="CheckForBuildTools" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-    <Import Project="..\dir.targets" />
+  <Import Project="..\dir.targets" />
+  <PropertyGroup>
+    <RuntimeIdGraphDefinitionFile>$(PackagesDir)$(PlatformPackageId.ToLowerInvariant())\$(MicrosoftNETCorePlatformsPackageVersion)\runtime.json</RuntimeIdGraphDefinitionFile>
+  </PropertyGroup>
+
+  <!-- Create deb tool package and set up the project that consumes it as a CLI tool. -->
+  <Target Name="InitializeDotnetDebTool">
+    <!--
+      Build the deb tool and initialize the consumer project. Using an MSBuild task helps this
+      to only happen once if parallel projects depend on this target.
+    -->
+    <MSBuild Projects="$(MSBuildThisFileDirectory)deps\init-deb-tool-consumer.proj" />
+  </Target>
+
+  <!--
+    Check if the deb package build tool prereq is available.
+    This issue tracks adding a way to raise this as a build error: https://github.com/dotnet/core-setup/issues/5396
+  -->
+  <Target Name="TestDebuild">
+    <!-- run Debuild -->
+    <Exec Command="/usr/bin/env debuild -h" ContinueOnError="true">
+      <Output TaskParameter="ExitCode" PropertyName="DebuildExitCode" />
+    </Exec>
+
+    <!-- Check if it successfully showed us a help message. -->
     <PropertyGroup>
-        <RuntimeIdGraphDefinitionFile>$(PackagesDir)$(PlatformPackageId.ToLowerInvariant())\$(MicrosoftNETCorePlatformsPackageVersion)\runtime.json</RuntimeIdGraphDefinitionFile>
+      <DebuildPresent>false</DebuildPresent>
+      <DebuildPresent Condition=" '$(DebuildExitCode)' == '0' ">true</DebuildPresent>
     </PropertyGroup>
+
+    <Message Condition=" '$(DebuildPresent)'  != 'true' "
+             Text="Debuild Not found, Debian packages will not be built."
+             Importance="High" />
+  </Target>
+
+  <!--
+    Check if the RPM package build tool prereq is available.
+    This issue tracks adding a way to raise this as a build error: https://github.com/dotnet/core-setup/issues/5396
+  -->
+  <Target Name="TestFPMTool">
+    <!-- run FPM  -->
+    <Exec Command="/usr/bin/env fpm -h" ContinueOnError="true">
+      <Output TaskParameter="ExitCode" PropertyName="FPMExitCode" />
+    </Exec>
+
+    <!-- Check if it successfully showed us a help message. -->
+    <PropertyGroup>
+      <FPMPresent>false</FPMPresent>
+      <FPMPresent Condition=" '$(FPMExitCode)' == '0' ">true</FPMPresent>
+    </PropertyGroup>
+
+    <Message Condition=" '$(FPMPresent)'  != 'true' "
+             Text="FPM tool Not found, RPM packages will not be built."
+             Importance="High" />
+  </Target>
 </Project>
index b68d12b..fa702d0 100644 (file)
@@ -8,6 +8,11 @@
     <MSBuild Targets="Build" Projects="@(Project)" Condition="'$(SerializeProjects)'!='true'" BuildInParallel="true" />
   </Target>
 
+  <Target Name="GenerateInstallers">
+    <MSBuild Targets="GenerateInstallers" Projects="@(Project)" Condition="'$(SerializeProjects)'=='true'" Properties="Dummy=%(Identity)"/>
+    <MSBuild Targets="GenerateInstallers" Projects="@(Project)" Condition="'$(SerializeProjects)'!='true'" BuildInParallel="true" />
+  </Target>
+
   <Target Name="Clean">
     <!-- To Serialize we use msbuild's batching functionality '%' to force it to batch all similar projects with the same identity 
          however since the project names are unique it will essentially force each to run in its own batch -->
index 64a80e3..0c57419 100644 (file)
@@ -1,10 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <dotnetDebToolSource>$(ProjectDir)tools-local/setuptools/dotnet-deb-tool/</dotnetDebToolSource>
-    <dotnetDebToolPackageSource>$(PackageOutputPath)</dotnetDebToolPackageSource>
-    <toolConsumerProjectName>dotnet-deb-tool-consumer.csproj</toolConsumerProjectName>
-    <consumingProjectDirectory>$(IntermediateOutputRootPath)$(toolConsumerProjectName)</consumingProjectDirectory>
     <debPackaginfConfigPath>$(PackagingRoot)deb/</debPackaginfConfigPath>
     <SharedHostDebPkgName>dotnet-host</SharedHostDebPkgName>
     <SharedHostDebPkgName>$(SharedHostDebPkgName.ToLower())</SharedHostDebPkgName>  
index 5aaa091..aeb5559 100644 (file)
@@ -5,39 +5,9 @@
   <UsingTask TaskName="ReplaceFileContents" AssemblyFile="$(LocalBuildToolsTaskDir)core-setup.tasks.dll"/>
 
   <PropertyGroup>
-    <IsDebianBasedDistro Condition="$(PackageTargetRid.StartsWith('ubuntu')) or
-                                    $(PackageTargetRid.StartsWith('debian')) or 
-                                    $(PackageTargetRid.StartsWith('linuxmint'))">true</IsDebianBasedDistro>
-    <BuildDebPackage Condition="'$(IsDebianBasedDistro)' == true and '$(TargetArchitecture)' == 'x64'">true</BuildDebPackage>
     <DebianConfigJsonName>debian_config.json</DebianConfigJsonName>
-    <BuildRuntimeDebs>true</BuildRuntimeDebs>
   </PropertyGroup>
 
-  <Target Name="InitializeDotnetDebTool">
-    <PropertyGroup>
-      <VersionSuffixArg Condition="'$(VersionSuffix)' != ''">--version-suffix $(VersionSuffix)</VersionSuffixArg>
-    </PropertyGroup>
-
-    <Exec Command="$(DotnetRestoreCommand) $(dotnetDebToolSource)"/>
-    <Exec Command="$(DotnetToolCommand) pack $(dotnetDebToolSource) --output $(PackageOutputPath) $(VersionSuffixArg)"/>
-
-    <RemoveDir Condition="Exists('$(consumingProjectDirectory)')"
-               Directories="$(consumingProjectDirectory)" />
-
-    <MakeDir Directories="$(consumingProjectDirectory)" />
-
-    <MakeDir Condition="!Exists('$(PackagesIntermediateDir)')"
-             Directories="$(PackagesIntermediateDir)" />
-
-
-    <Copy SourceFiles="$(MSBuildThisFileDirectory)/$(toolConsumerProjectName)"
-          DestinationFiles="$(consumingProjectDirectory)/$(toolConsumerProjectName)" />
-
-    <Exec Command="$(DotnetRestoreCommand) -s $(dotnetDebToolPackageSource)"
-          WorkingDirectory="$(consumingProjectDirectory)" />
-
-  </Target>
-
   <Target Name="GenerateDebs"
           DependsOnTargets="TestDebuild;
                             BuildDebs;"
                          ReplacementItems="@(SharedHostTokenValue)" />
 
     <Exec Command="$(DotnetToolCommand) deb-tool -i $(debLayoutDirectory) -o $(debIntermediatesDir) -n $(DebPackageName) -v $(DebPackageVersion)"
-          WorkingDirectory="$(consumingProjectDirectory)" />
+          WorkingDirectory="$(DebtoolConsumerDeployDir)" />
 
     <!-- Copy package to output -->
     <ItemGroup>
                          ReplacementItems="@(HostFxrTokenValue)" />
 
     <Exec Command="$(DotnetToolCommand) deb-tool -i $(debLayoutDirectory) -o $(debIntermediatesDir) -n $(DebPackageName) -v $(DebPackageVersion)"
-          WorkingDirectory="$(consumingProjectDirectory)" />
+          WorkingDirectory="$(DebtoolConsumerDeployDir)" />
 
     <!-- Copy package to output -->
     <ItemGroup>
                          ReplacementItems="@(SharedFrameworkTokenValue)" />
 
     <Exec Command="$(DotnetToolCommand) deb-tool -i $(debLayoutDirectory) -o $(debIntermediatesDir) -n $(DebPackageName) -v $(DebPackageVersion)"
-          WorkingDirectory="$(consumingProjectDirectory)" />
+          WorkingDirectory="$(DebtoolConsumerDeployDir)" />
 
     <!-- Copy package to output -->
     <ItemGroup>
                          ReplacementItems="@(SharedFrameworkTokenValue)" />
 
     <Exec Command="$(DotnetToolCommand) deb-tool -i $(debLayoutDirectory) -o $(debIntermediatesDir) -n $(DebPackageName) -v $(DebPackageVersion)"
-          WorkingDirectory="$(consumingProjectDirectory)" />
+          WorkingDirectory="$(DebtoolConsumerDeployDir)" />
 
     <!-- Copy package to output -->
     <ItemGroup>
 
   </Target>
 
-  <Target Name="TestDebuild">
-
-    <!-- run Debuild -->
-    <Exec Command="/usr/bin/env debuild -h" ContinueOnError="true">
-      <Output TaskParameter="ExitCode" PropertyName="DebuildExitCode" />
-    </Exec>
-
-    <!-- Check if it returned 0 -->
-    <PropertyGroup>
-      <DebuildPresent>false</DebuildPresent>
-      <DebuildPresent Condition=" '$(DebuildExitCode)' == '0' ">true</DebuildPresent>
-    </PropertyGroup>
-
-    <!-- Workaround for Jenkins machines that don't have the necessary packages https://github.com/dotnet/core-setup/issues/2260 -->
-    <Message Condition=" '$(DebuildPresent)'  != 'true' "
-             Text="Debuild Not found, Debian packages will not be built."
-             Importance="High" />
-  </Target>
-
 </Project>
\ No newline at end of file
index 6fe7bed..6141233 100644 (file)
@@ -5,10 +5,11 @@
 
   <PropertyGroup>
     <PackageTargets>
-        GenerateVersionBadge;
-        GenerateCompressedFiles;
-        GenerateNugetPackages;
-        GenerateInstallers;
+      GenerateVersionBadge;
+      GenerateCompressedFiles;
+      GenerateNugetPackages;
+      GenerateInstallers;
+      GenerateProjectInstallers;
     </PackageTargets>
   </PropertyGroup>
 
 
   <Target Name="GenerateCombinedInstallers" DependsOnTargets="InitPackage;GenerateBundles" />
 
+  <!-- Run the project-based installer creation infrastructure as well. -->
+  <Target Name="GenerateProjectInstallers">
+    <MSBuild Targets="GenerateInstallers" Projects="..\projects\installer.builds" />
+  </Target>
+
   <Target Name="GenerateNugetPackages" DependsOnTargets="InitPackage" Condition="'$(UsePrebuiltPortableBinariesForInstallers)' == 'false'">
 
     <ItemGroup>
index 63f8f6f..b69f196 100644 (file)
@@ -7,9 +7,12 @@
 
     <!-- Package prebuilt binaries -->
     <UsePrebuiltPortableBinariesForInstallers>false</UsePrebuiltPortableBinariesForInstallers>
+
+    <!-- The runtime package is distro-independent. (Runtime-deps packages vary per distro.) -->
+    <BuildRuntimeDebs>$(BuildDistroIndependentInstallers)</BuildRuntimeDebs>
+    <BuildRuntimeRpms>$(BuildDistroIndependentInstallers)</BuildRuntimeRpms>
     
     <!-- Use trailing '/'. Heat.exe doesn't accept a trailing '\'.-->
-    <PackagesIntermediateDir>$(IntermediateOutputRootPath)packages/</PackagesIntermediateDir>
     <SharedHostPublishRoot>$(IntermediateOutputRootPath)sharedHost/</SharedHostPublishRoot>
     <HostFxrPublishRoot>$(IntermediateOutputRootPath)hostFxr/</HostFxrPublishRoot>
     <SharedFrameworkPublishRoot>$(IntermediateOutputRootPath)sharedFx/</SharedFrameworkPublishRoot>
index 2efbd93..50e7f01 100644 (file)
@@ -12,7 +12,7 @@
     <RuntimeDependenciesRpmPkgName>$(DotnetRuntimeDependenciesPackageString)$(PackageNameSuffix)</RuntimeDependenciesRpmPkgName>
     <RuntimeDependenciesRpmPkgName>$(RuntimeDependenciesRpmPkgName.ToLower())</RuntimeDependenciesRpmPkgName>
 
-    <TemplatesDir>$(MSBuildThisFileDirectory)/templates</TemplatesDir>
+    <TemplatesDir>$(RpmTemplatesDir)</TemplatesDir>
     <ScriptsDir>$(MSBuildThisFileDirectory)/scripts</ScriptsDir>
 
   </PropertyGroup>
index c4f19e4..e590351 100644 (file)
@@ -5,12 +5,6 @@
   <UsingTask TaskName="ReplaceFileContents" AssemblyFile="$(LocalBuildToolsTaskDir)core-setup.tasks.dll"/>
   <UsingTask TaskName="BuildFPMToolPreReqs" AssemblyFile="$(LocalBuildToolsTaskDir)core-setup.tasks.dll"/>
 
-  <PropertyGroup>
-    <IsRPMBasedDistro Condition="'$(InstallerExtension)' == '.rpm'">true</IsRPMBasedDistro>
-    <BuildRpmPackage Condition="'$(IsRPMBasedDistro)' == true and '$(TargetArchitecture)' == 'x64'">true</BuildRpmPackage>
-    <BuildRuntimeRpms>true</BuildRuntimeRpms>
-  </PropertyGroup>
-
   <Target Name="GenerateRpms"
           DependsOnTargets="TestFPMTool;BuildRpms;"
           Condition="'$(BuildRpmPackage)'=='true'" />
 
   </Target>
 
-  <Target Name="TestFPMTool">
-
-    <!-- run FPM  -->
-    <Exec Command="/usr/bin/env fpm -h" ContinueOnError="true">
-      <Output TaskParameter="ExitCode" PropertyName="FPMExitCode" />
-    </Exec>
-
-    <!-- Check if it returned 0 -->
-    <PropertyGroup>
-      <FPMPresent>false</FPMPresent>
-      <FPMPresent Condition=" '$(FPMExitCode)' == '0' ">true</FPMPresent>
-    </PropertyGroup>
-
-    <!-- Workaround for Jenkins machines that don't have the necessary packages https://github.com/dotnet/core-setup/issues/2260 -->
-    <Message Condition=" '$(FPMPresent)'  != 'true' "
-             Text="FPM tool Not found, RPM packages will not be built."
-             Importance="High" />
-  </Target>
 </Project>
index caad2a1..375b5c6 100644 (file)
@@ -5,6 +5,13 @@
     <PackagePlatform>AnyCPU</PackagePlatform>
     <IntermediateOutputPath Condition="'$(IntermediateOutputPath)' == ''">obj/$(Configuration)/</IntermediateOutputPath>
     <IntermediateOutputPath Condition="'$(NuGetRuntimeIdentifier)' != ''">$(IntermediateOutputPath)$(NuGetRuntimeIdentifier)/</IntermediateOutputPath>
+
+    <!--
+      Allow packaging one build's outputs for a different RID. For example, package portable bits
+      for multiple Debian-based distros.
+    -->
+    <InstallerSourceOSPlatformConfig Condition="'$(InstallerSourceOSPlatformConfig)' == ''">$(OSPlatformConfig)</InstallerSourceOSPlatformConfig>
+    <InstallerSourceIntermediateOutputDir>$(BaseIntermediateOutputPath)$(InstallerSourceOSPlatformConfig)\$(MSBuildProjectName)\</InstallerSourceIntermediateOutputDir>
     
     <SkipPackageFileCheck>true</SkipPackageFileCheck>
     <!-- This property must be set to the same value as $(PackageOutputPath) for the nuspecs and nupkgs to be binplaced to the intended location. -->
     <PackProjectDependencies>true</PackProjectDependencies>
   </PropertyGroup>
 
+  <!-- In *.builds projects, the current phase's name is the same as the project name. -->
+  <PropertyGroup>
+    <BuildPhase>$(MSBuildProjectName)</BuildPhase>
+  </PropertyGroup>
+
   <ItemGroup Condition="'$(DotNetBuildFromSource)' == 'true'">
     <!-- WindowsDesktop packs currently depend on non-source-built App package. -->
     <ProjectExclusions Include="windowsdesktop\**\*.*proj" />
@@ -65,7 +77,7 @@
 
   <!-- Detect framework pack suffix and apply defaults. -->
   <PropertyGroup Condition="$(MSBuildProjectName.EndsWith('.Ref'))">
-    <IsTargetingPack>true</IsTargetingPack>
+    <FrameworkPackType>targeting</FrameworkPackType>
 
     <!--
       Prevent 'runtime.<rid>.Microsoft.<framework>.App.Ref' packages from being built.
     <ExcludeLineupReference>true</ExcludeLineupReference>
     <PackProjectDependencies>false</PackProjectDependencies>
 
-    <!-- Location to lay out pack contents. Used later to create installers. -->
-    <PackLayoutDir>$(IntermediateOutputPath)layout/</PackLayoutDir>
+    <!-- Location to lay out pack contents, and where to grab bits to create installers. -->
+    <PackLayoutDir>$(InstallerSourceIntermediateOutputDir)layout/$(FrameworkPackType)/</PackLayoutDir>
+
+    <InstallerName>$(ShortFrameworkName)-targeting-pack</InstallerName>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <VersionedInstallerName>$(InstallerName)-$(PackageNameSuffix)</VersionedInstallerName>
+    <VersionedInstallerName>$(VersionedInstallerName.ToLowerInvariant())</VersionedInstallerName>
+    <InstallerPackageRelease>1</InstallerPackageRelease>
+
+    <InstallerPackageVersion>$(ProductionVersion)</InstallerPackageVersion>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(InstallerExtension)' == '.deb'">
+    <InstallerPackageVersion Condition="'$(IncludePreReleaseLabelInPackageVersion)' == 'true'">$(ProductionVersion)~$(VersionSuffix)</InstallerPackageVersion>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(InstallerExtension)' == '.rpm'">
+    <InstallerPackageRelease Condition="'$(IncludePreReleaseLabelInPackageVersion)' == 'true'">0.1.$(VersionSuffix)</InstallerPackageRelease>
+    <InstallerPackageRelease>$([System.String]::Copy('$(InstallerPackageRelease)').Replace('-', '_'))</InstallerPackageRelease>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <InstallerIntermediatesDir>$(PackagesIntermediateDir)$(InstallerName)/$(InstallerPackageVersion)/</InstallerIntermediatesDir>
+
+    <InstallerBuildPart>$(ProductMoniker)</InstallerBuildPart>
+    <InstallerBuildPart Condition="'$(InstallerExtension)' == '.deb' or '$(InstallerExtension)' == '.rpm'"
+      >$(SharedFrameworkNugetVersion)-$(TargetArchitecture)</InstallerBuildPart>
+
+    <!-- Location to place the installer, in bin. -->
+    <InstallerFile>$(AssetOutputPath)$(InstallerName)-$(InstallerBuildPart)$(InstallerExtension)</InstallerFile>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.depproj'">
   <ItemGroup Condition="'$(PackageTargetRuntime)' == '' and '$(HasRuntimePackages)' != 'false'">
     <ProjectReference Include="@(RuntimeProject)" />
   </ItemGroup>
+
+  <ItemGroup>
+    <!--
+      Create Debian and RPM packages for targeting packs. Similar to the runtime package, we only
+      make one for all distros.
+    -->
+    <InstallerPackageProject
+      Include="$(MSBuildProjectFullPath)"
+      Condition="
+        '$(FrameworkPackType)' == 'targeting' AND
+        '$(BuildDistroIndependentInstallers)' == 'true'" />
+  </ItemGroup>
   
 </Project>
\ No newline at end of file
index 552318f..86ddb62 100644 (file)
   -->
   <Target Name="GetSymbolPackageFiles" BeforeTargets="GetPackageFiles">
     <ItemGroup Condition="'$(SymbolFileExtension)' != ''">
-      <AdditionalLibPackageExcludes Include="%2A%2A\%2a$(SymbolFileExtension)" />
+      <AdditionalLibPackageExcludes Include="%2A%2A\%2A$(SymbolFileExtension)" />
+    </ItemGroup>
+    <ItemGroup Condition="'$(CrossGenSymbolExtension)' != ''">
+      <AdditionalLibPackageExcludes Include="%2A%2A\%2A$(CrossGenSymbolExtension)" />
     </ItemGroup>
 
     <ItemGroup>
   </Target>
 
   <!--
-    Copy the files in the package's data/ dir to a layout directory. This is what the targeting pack
-    installer will place in the dotnet install dir.
-
-    This extracts from the nupkg (zip) directly. An alternative would be using the PackageFile
-    items, but there's some risk of handling TargetPath metadata differently than NuGet does. Using
-    the nupkg directly ensures results are identical.
-  -->
-  <Target Name="CreateTargetingPackLayout"
-          AfterTargets="CreatePackage"
-          Condition="'$(IsTargetingPack)' == 'true'">
-    <PropertyGroup>
-      <TargetingPackNupkgFile>$(PackageOutputPath)$(Id).$(PackageVersion).nupkg</TargetingPackNupkgFile>
-    </PropertyGroup>
-
-    <ZipFileGetEntries TargetArchive="$(TargetingPackNupkgFile)">
-      <Output TaskParameter="Entries" ItemName="TargetingPackNupkgEntries" />
-    </ZipFileGetEntries>
-
-    <ItemGroup>
-      <TargetingPackDataEntries
-        Include="@(TargetingPackNupkgEntries)"
-        Condition="
-          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('data/')) OR
-          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('ref/')) OR
-          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('runtimes/'))" />
-    </ItemGroup>
-
-    <ZipFileExtractToDirectory
-      SourceArchive="$(TargetingPackNupkgFile)"
-      DestinationDirectory="$(PackLayoutDir)packs/$(Id)/$(Version)/"
-      OverwriteDestination="true"
-      Include="@(TargetingPackDataEntries)" />
-  </Target>
-
-  <!--
     Get Project items: build the project, and build runtime packages if necessary. All projects in
     this directory are ProjectProviders.
   -->
   <Target Name="GetProjectsToBuild" Returns="@(Project)">
     <ItemGroup>
-      <Project Include="@(RuntimeProject)" Condition="'$(BuildRuntimePackages)' == 'true'" />
-      <Project Include="$(MSBuildProjectFullPath)" />
+      <!-- The pkg and src builds both build these projects. Add them to both phases. -->
+      <PkgSrcProject Include="@(RuntimeProject)" Condition="'$(BuildRuntimePackages)' == 'true'" />
+      <PkgSrcProject Include="$(MSBuildProjectFullPath)" />
+
+      <Project Include="@(PkgSrcProject)" Phase="pkg" />
+      <Project Include="@(PkgSrcProject)" Phase="src" />
+      <Project Include="@(InstallerPackageProject)" Phase="installer" />
     </ItemGroup>
   </Target>
 
index c9c88c7..3597ba5 100644 (file)
@@ -9,10 +9,17 @@
     use this to build runtime packages, for example.
   -->
   <Target Name="ExpandProjects"
-          BeforeTargets="Build">
+          BeforeTargets="Build;GenerateInstallers">
     <MSBuild Projects="@(ProjectProvider)" Targets="GetProjectsToBuild">
-      <Output TaskParameter="TargetOutputs" ItemName="Project" />
+      <Output TaskParameter="TargetOutputs" ItemName="_everyProject" />
     </MSBuild>
+
+    <!-- Filter to projects that apply to the current *.builds file. -->
+    <ItemGroup>
+      <Project
+        Include="@(_everyProject)"
+        Condition="'%(_everyProject.Phase)' == '$(BuildPhase)'" />
+    </ItemGroup>
   </Target>
 
   <!--
index dc0f766..334c4ae 100644 (file)
@@ -3,6 +3,266 @@
     Shared targets specific to projects building 'Microsoft.*.App*' packages.
   -->
 
+  <UsingTask TaskName="BuildFPMToolPreReqs" AssemblyFile="$(LocalBuildToolsTaskDir)core-setup.tasks.dll"/>
+  <UsingTask TaskName="GenerateJsonObjectString" AssemblyFile="$(LocalBuildToolsTaskDir)core-setup.tasks.dll"/>
+
+  <Target Name="GenerateInstallers"
+          DependsOnTargets="
+            GenerateDeb;
+            GenerateRpm" />
+
+  <Target Name="GenerateDeb" DependsOnTargets="TestDebuild;CreateDeb" />
+  <Target Name="GenerateRpm" DependsOnTargets="TestFPMTool;CreateRpm" />
+
+  <!--
+    Create Debian package.
+  -->
+  <Target Name="CreateDeb"
+          DependsOnTargets="
+            InitializeDotnetDebTool;
+            CreateInstallerLayout;
+            GetDebInstallerJsonProperties"
+          Condition="'$(BuildDebPackage)' == 'true' AND '$(DebuildPresent)' == 'true'">
+    <PropertyGroup>
+      <ConfigJsonFile>$(LayoutDirectory)debian_config.json</ConfigJsonFile>
+      <DebIntermediatesDir>$(InstallerIntermediatesDir)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="@(CommonJsonProperty);@(DebJsonProperty)"
+      TargetFile="$(ConfigJsonFile)" />
+
+    <!-- Run deb tool in the directory of the consumer project. -->
+    <Exec
+      Command="$(DotnetToolCommand) deb-tool $(DebToolArgs)"
+      WorkingDirectory="$(DebtoolConsumerDeployDir)" />
+
+    <!-- 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" />
+  </Target>
+
+  <Target Name="GetDebInstallerJsonProperties"
+          DependsOnTargets="GetCommonJsonProperties">
+    <ItemGroup>
+      <DebJsonProperty Include="debian_dependencies" Object="{}" />
+    </ItemGroup>
+  </Target>
+
+  <!--
+    Create RPM package.
+  -->
+  <Target Name="CreateRpm"
+          DependsOnTargets="
+            CreateInstallerLayout;
+            GetRpmInstallerJsonProperties"
+          Condition="'$(BuildRpmPackage)' == 'true' AND '$(FPMPresent)' == 'true'">
+    <PropertyGroup>
+      <ConfigJsonFile>$(LayoutDirectory)rpm_config.json</ConfigJsonFile>
+      <RpmIntermediatesDir>$(InstallerIntermediatesDir)out-rpm</RpmIntermediatesDir>
+
+      <!-- Copyright, Changelog -->
+      <RpmTemplatesLayoutDir>$(LayoutDirectory)templates/</RpmTemplatesLayoutDir>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <RpmTemplateFile Include="$(RpmTemplatesDir)**/*" />
+    </ItemGroup>
+
+    <Copy
+      SourceFiles="@(RpmTemplateFile)"
+      DestinationFiles="@(RpmTemplateFile->'$(RpmTemplatesLayoutDir)%(RecursiveDir)%(Filename)%(Extension)')" />
+
+    <GenerateJsonObjectString
+      Properties="@(CommonJsonProperty);@(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" />
+  </Target>
+
+  <Target Name="GetRpmInstallerJsonProperties"
+          DependsOnTargets="GetCommonJsonProperties">
+    <ItemGroup>
+      <RpmJsonProperty Include="vendor" String=".NET Foundation" />
+      <RpmJsonProperty Include="install_doc" String="/usr/share/doc/$(VersionedInstallerName)/" />
+      <RpmJsonProperty Include="rpm_dependencies" Object="{}" />
+    </ItemGroup>
+  </Target>
+
+  <!--
+    Create installer layout. Used for RPM or Debian package creation.
+  -->
+  <Target Name="CreateInstallerLayout"
+          DependsOnTargets="CopyFilesToLayout;FixLayoutPermissions" />
+
+  <Target Name="CopyFilesToLayout">
+    <PropertyGroup>
+      <LayoutDirectory>$(InstallerIntermediatesDir)/debianLayoutDirectory/</LayoutDirectory>
+      <LayoutAbsolute>$(LayoutDirectory)$</LayoutAbsolute>
+      <LayoutPackageRoot>$(LayoutDirectory)package_root</LayoutPackageRoot>
+      <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)" />
+
+    <!-- Copy files to layout. -->
+    <ItemGroup>
+      <LayoutFiles Include="$(PackLayoutDir)/**/*" />
+    </ItemGroup>
+
+    <Error Text="No pack layout files found, expected > 0." Condition="@(LayoutFiles->Count()) == 0" />
+
+    <Copy
+      SourceFiles="@(LayoutFiles)"
+      DestinationFiles="@(LayoutFiles->'$(LayoutPackageRoot)/%(RecursiveDir)%(Filename)%(Extension)')" />
+  </Target>
+
+  <Target Name="FixLayoutPermissions"
+          Condition="'$(OSGroup)' != 'Windows_NT'">
+    <!-- Fix file permissions in the layout dir. -->
+    <!-- Reset everything to user readable/writeable and group and world readable. -->
+    <Exec Command='find "$(PackLayoutDir)" -type f -name "*" -exec chmod 644 {} \;' />
+    <!-- Generally, dylibs and sos have 'x'. -->
+    <Exec Command='find "$(PackLayoutDir)" -type f -name "*.dylib" -exec chmod 755 {} \;' />
+    <Exec Command='find "$(PackLayoutDir)" -type f -name "*.so" -exec chmod 755 {} \;' />
+    <!-- Executables (those without dots) are executable. -->
+    <Exec Command='find "$(PackLayoutDir)" -type f ! -name "*.*" -exec chmod 755 {} \;' />
+  </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('$(ProjectDir)LICENSE.TXT').Replace('%0A', '\n').Replace('"', '\"'))</FullLicenseText>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <JsonReleaseProperty Include="package_version" String="1.0.0.0" />
+      <JsonReleaseProperty Include="package_revision" String="$(InstallerPackageRelease)" />
+      <JsonReleaseProperty Include="urgency" String="low" />
+      <JsonReleaseProperty Include="changelog_message" String="https://github.com/dotnet/core/tree/master/release-notes" />
+
+      <JsonControlProperty Include="priority" String="standard" />
+      <JsonControlProperty Include="section" String="libs" />
+      <JsonControlProperty Include="architecture" String="amd64" />
+
+      <JsonLicenseProperty Include="type" String="MIT and ASL 2.0 and BSD" />
+      <JsonLicenseProperty Include="full_text" String="$(FullLicenseText)" />
+    </ItemGroup>
+
+    <GenerateJsonObjectString Properties="@(JsonReleaseProperty)">
+      <Output TaskParameter="Json" PropertyName="JsonReleaseObject" />
+    </GenerateJsonObjectString>
+    <GenerateJsonObjectString Properties="@(JsonControlProperty)">
+      <Output TaskParameter="Json" PropertyName="JsonControlObject" />
+    </GenerateJsonObjectString>
+    <GenerateJsonObjectString Properties="@(JsonLicenseProperty)">
+      <Output TaskParameter="Json" PropertyName="JsonLicenseObject" />
+    </GenerateJsonObjectString>
+
+    <ItemGroup>
+      <CommonJsonProperty Include="package_name" String="$(VersionedInstallerName)" />
+      <CommonJsonProperty Include="short_description" String="$(MSBuildProjectName) $(InstallerPackageVersion)" />
+      <CommonJsonProperty Include="maintainer_name" String=".NET Core Team" />
+      <CommonJsonProperty Include="maintainer_email" String="dotnetpackages@dotnetfoundation.org" />
+      <CommonJsonProperty Include="install_root" String="/usr/share/dotnet" />
+      <CommonJsonProperty Include="long_description" String=".NET Core 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." />
+      <CommonJsonProperty Include="homepage" String="https://github.com/dotnet/core" />
+      <CommonJsonProperty Include="copyright" String="2017 Microsoft" />
+      <CommonJsonProperty Include="release" Object="$(JsonReleaseObject)" />
+      <CommonJsonProperty Include="control" Object="$(JsonControlObject)" />
+      <CommonJsonProperty Include="license" Object="$(JsonLicenseObject)" />
+    </ItemGroup>
+  </Target>
+
+  <!--
+    Copy the files in the package's data/ dir to a layout directory. This is what the targeting pack
+    installer will place in the dotnet install dir.
+
+    This extracts from the nupkg (zip) directly. An alternative would be using the PackageFile
+    items, but there's some risk of handling TargetPath metadata and filtering symbols differently
+    than the NuGet tasks do. Using the nupkg directly ensures results are identical.
+
+    This is not ideal: the dependency on NuGet package creation is avoidable. We could create (a)
+    layout(s) that both NuGet packaging and installers are based on.
+  -->
+  <Target Name="CreateFrameworkPackLayout"
+          AfterTargets="CreatePackage"
+          Condition="'$(FrameworkPackType)' != ''">
+    <PropertyGroup>
+      <TargetingPackNupkgFile>$(PackageOutputPath)$(Id).$(PackageVersion).nupkg</TargetingPackNupkgFile>
+    </PropertyGroup>
+
+    <ZipFileGetEntries TargetArchive="$(TargetingPackNupkgFile)">
+      <Output TaskParameter="Entries" ItemName="TargetingPackNupkgEntries" />
+    </ZipFileGetEntries>
+
+    <ItemGroup>
+      <TargetingPackDataEntries
+        Include="@(TargetingPackNupkgEntries)"
+        Condition="
+          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('data/')) OR
+          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('ref/')) OR
+          $([System.String]::new('%(TargetingPackNupkgEntries.Identity)').StartsWith('runtimes/'))" />
+    </ItemGroup>
+
+    <ZipFileExtractToDirectory
+      SourceArchive="$(TargetingPackNupkgFile)"
+      DestinationDirectory="$(PackLayoutDir)packs/$(Id)/$(Version)/"
+      OverwriteDestination="true"
+      Include="@(TargetingPackDataEntries)" />
+  </Target>
+
   <!-- Target overrides (can't be shared with other package projects) -->
 
   <Target Name="GetPackageReport" />
diff --git a/src/installer/pkg/projects/installer.builds b/src/installer/pkg/projects/installer.builds
new file mode 100644 (file)
index 0000000..6f29147
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="GenerateInstallers" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+
+  <ItemGroup>
+    <ProjectProvider Include="**\*.pkgproj" Exclude="@(ProjectExclusions)" />
+  </ItemGroup>
+
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.traversal.targets))\dir.traversal.targets" />
+</Project>
index 185c56b..83db445 100644 (file)
@@ -2,6 +2,7 @@
 <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <IsFrameworkPackage>true</IsFrameworkPackage>
+    <ShortFrameworkName>dotnet</ShortFrameworkName>
   </PropertyGroup>
 
   <Import Project="..\..\dir.props" />
index 4157e3c..2d57366 100644 (file)
@@ -2,10 +2,16 @@
 <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <IsFrameworkPackage>true</IsFrameworkPackage>
+    <ShortFrameworkName>windowsdesktop</ShortFrameworkName>
   </PropertyGroup>
 
   <Import Project="..\..\dir.props" />
 
+  <PropertyGroup>
+    <BuildDebPackage>false</BuildDebPackage>
+    <BuildRpmPackage>false</BuildRpmPackage>
+  </PropertyGroup>
+
   <!-- Redistribute package content from other nuget packages. -->
   <ItemGroup>
     <ProjectReference Include="..\src\windowsdesktop.depproj">
diff --git a/tools-local/scripts/dev-build-deb-docker.sh b/tools-local/scripts/dev-build-deb-docker.sh
new file mode 100755 (executable)
index 0000000..4d88fc0
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+# WARNING: This utility is not used by infra and very likely to be out of date.
+
+# This is a simple dev utility to easily perform clean builds for Debian and RPM
+# package development. It emulates the official build, first producing a
+# portable build using some other image, then using Deb/RPM build images to
+# package up the bits.
+#
+# Run this script from the root of the repository.
+
+set -uex
+
+skipPortable=
+
+while [[ $# > 0 ]]; do
+    opt="$(echo "$1" | awk '{print tolower($0)}')"
+    case "$opt" in
+        --skip-portable)
+            skipPortable=true
+            ;;
+        *)
+            echo "Invalid argument: $1"
+            exit 1
+            ;;
+    esac
+    shift
+done
+
+containerized() {
+    image=$1
+    shift
+    docker run -it --rm \
+        -u="$(id -u):$(id -g)" \
+        -e HOME=/work/.container-home \
+        -v "$(pwd):/work:z" \
+        -w "/work" \
+        "$image" \
+        "$@"
+}
+
+package() {
+    name=$1
+    shift
+    image=$1
+    shift
+    type=$1
+    shift
+    queryCommand=$1
+    shift
+
+    containerized "$image" \
+        Tools/msbuild.sh \
+        build.proj \
+        /t:BuildTraversalBuildDependencies \
+        /p:ConfigurationGroup=Release \
+        /p:OSGroup=Linux \
+        /p:PortableBuild=false \
+        /p:TargetArchitecture=x64 \
+        "/bl:bin/msbuild.$name.traversaldependencies.binlog"
+
+    containerized "$image" \
+        Tools/msbuild.sh \
+        src/pkg/packaging/dir.proj \
+        /p:UsePrebuiltPortableBinariesForInstallers=true \
+        /p:SharedFrameworkPublishDir=/work/bin/obj/linux-x64.Release/sharedFrameworkPublish/ \
+        /p:InstallerSourceOSPlatformConfig=linux-x64.Release \
+        /p:GenerateProjectInstallers=true \
+        /p:ConfigurationGroup=Release \
+        /p:OSGroup=Linux \
+        /p:PortableBuild=false \
+        /p:TargetArchitecture=x64 \
+        "/bl:bin/msbuild.$name.installers.binlog"
+
+    containerized "$image" \
+        find bin/*Release/ \
+        -iname "*.$type" \
+        -exec printf "\n{}\n========\n" \; \
+        -exec $queryCommand '{}' \; \
+        > "info-$type.txt"
+}
+
+[ "$skipPortable" ] || containerized microsoft/dotnet-buildtools-prereqs:centos-7-b46d863-20180719033416 \
+    ./build.sh \
+    -skiptests=true \
+    -ConfigurationGroup=Release \
+    -PortableBuild=true \
+    -strip-symbols \
+    -TargetArchitecture=x64 \
+    -- \
+    /bl:bin/msbuild.portable.binlog
+
+ubuntu=microsoft/dotnet-buildtools-prereqs:ubuntu-14.04-debpkg-e5cf912-20175003025046
+rhel=microsoft/dotnet-buildtools-prereqs:rhel-7-rpmpkg-c982313-20174116044113
+
+package ubuntu $ubuntu deb "dpkg-deb -I"
+package rhel $rhel rpm "rpm -qpiR"
index d04cb8c..f0c9fe8 100644 (file)
@@ -1,14 +1,14 @@
 // Copyright (c) .NET Foundation and contributors. All rights reserved.
 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
+using Microsoft.Build.Framework;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
 using System;
 using System.Collections.Generic;
-using Microsoft.Build.Utilities;
-using Microsoft.Build.Framework;
 using System.IO;
 using System.Linq;
 using System.Text;
-using Newtonsoft.Json;
 
 namespace Microsoft.DotNet.Build.Tasks
 {
@@ -61,7 +61,7 @@ namespace Microsoft.DotNet.Build.Tasks
             }
             catch (Exception e)
             {
-                Log.LogError("Exception while processing RPM paramters: " + e.Message);
+                Log.LogErrorFromException(e, true);
             }
 
             return !Log.HasLoggedErrors;
@@ -95,7 +95,7 @@ namespace Microsoft.DotNet.Build.Tasks
             }
         }
 
-        public void UpdateCopyRight(ConfigJson configJson)
+        private void UpdateCopyRight(ConfigJson configJson)
         {
             try
             {
@@ -152,7 +152,31 @@ namespace Microsoft.DotNet.Build.Tasks
             // Build the list of dependencies as -d <dep1> -d <dep2>
             if (configJson.Rpm_Dependencies != null)
             {
-                foreach (RpmDependency rpmdep in configJson.Rpm_Dependencies)
+                IEnumerable<RpmDependency> dependencies;
+
+                switch (configJson.Rpm_Dependencies)
+                {
+                    case JArray dependencyArray:
+                        dependencies = dependencyArray.ToObject<RpmDependency[]>();
+                        break;
+
+                    case JObject dependencyDictionary:
+                        dependencies = dependencyDictionary
+                            .ToObject<Dictionary<string, string>>()
+                            .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 != "")
@@ -167,7 +191,10 @@ namespace Microsoft.DotNet.Build.Tasks
                             dependency = string.Concat(rpmdep.Package_Name, " >= ", rpmdep.Package_Version);
                         }
                     }
-                    if (dependency != "") parameters.Add(string.Concat("-d ", EscapeArg(dependency)));
+                    if (dependency != "")
+                    {
+                        parameters.Add(string.Concat("-d ", EscapeArg(dependency)));
+                    }
                 }
             }
 
@@ -267,54 +294,58 @@ namespace Microsoft.DotNet.Build.Tasks
             }
             return false;
         }
-    }
 
-    /// <summary>
-    /// Model classes for reading and storing the JSON. 
-    /// </summary>
-    public 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 List<RpmDependency> Rpm_Dependencies { get; set; }
-        public List<string> Package_Conflicts { get; set; }
-        public List<string> Directories { get; set; }
-        public string After_Install_Source { get; set; }
-        public string After_Remove_Source { get; set; }
-    }
-    public 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; }
-    }
-    public class Control
-    {
-        public string Priority { get; set; }
-        public string Section { get; set; }
-        public string Architecture { get; set; }
-    }
-    public class License
-    {
-        public string Type { get; set; }
-        public string Full_Text { get; set; }
-    }
-    public class RpmDependency
-    {
-        public string Package_Name { get; set; }
-        public string Package_Version { get; set; }
+        /// <summary>
+        /// Model classes for reading and storing the JSON. 
+        /// </summary>
+        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<string> Package_Conflicts { get; set; }
+            public List<string> 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/tools-local/tasks/GenerateJsonObjectString.cs b/tools-local/tasks/GenerateJsonObjectString.cs
new file mode 100644 (file)
index 0000000..558c4b1
--- /dev/null
@@ -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
+{
+    public class GenerateJsonObjectString : BuildTask
+    {
+        private static readonly string __indent1 = new string(' ', 4);
+        private static readonly string __indent2 = new string(' ', 8);
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        [Required]
+        public ITaskItem[] Properties { get; set; }
+
+        /// <summary>
+        /// If set, also write the output JSON string to this file.
+        /// </summary>
+        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");
+            }
+        }
+    }
+}