Search code examples
c#.netvisual-studiomsbuildnuget

How to get a list of nuget packages installed via MSBuild macro?


From following question How do I list all installed NuGet packages?

I need to get a list of nuget packages from MSBuild itself.

What I try to do:

<Target Name="test" AfterTargets="ResolveReferences">
        <ItemGroup>
  <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths)"/>
</ItemGroup>

    <Message Text="Files @(BuildOutputInPackage -> '%(identity)', ', ')" Importance="high"/>

</Target>

Above code return all references whether its nuget or project references dlls. How exactly to return a list of nuget package that currently project *.csproj used?

Format i.e: packageid:Portable.BouncyCastle version = "1.9.0"

I use old classic non-sdk csproj template and .NET 4.8 and <PackageReference> not packages.config files.

Its very bad to read *.csproj and do regex to get a list of <packageReferences. Maybe there's a recommended MSBuild macro to do this like code shown above <Target....


Solution

  • Very thankful to Jonathan Dodds for his answer above. I combined some approach with his @(PackageReference) idea to get a version of currently referenced nuget package in old classic csproj template. Because a referenced version will not work with @(PackageReference) only. Also dotnet list not worked well in old classic csproj template (non-sdk).

    A code below do following:

    (Note this approach is not 100% organic, MSBuild should provide a simple target for that):

    1. Get all references that maybe be nuget or non-nuget (DLLs, etc).
    2. Get only references that included inside @(PackageReference). Jonathan Doods approach above.
    3. Get .nuspec file for that package to know exactly what version current *.csproj referenced.
    4. Create a new nuspec file for current project. with a list of dependencies.
    5. You can then call nuget.exe pack ProjectPath.csproj a ProjectName.nupkg nuget will generated.

    Use this code inside Directory.Build.targets or within your .csproj file.

        <Target Name="GenerateNuspecFile">
        <ItemGroup>
            <!-- Get all references -->
            <MyReference Include="@(ReferenceCopyLocalPaths)" Condition="%(extension) == '.dll'">
                <FirstDir>$([System.IO.Directory]::GetParent(%(RelativeDir)))</FirstDir>
                <LibDir>$([System.IO.Path]::GetFileNameWithoutExtension(%(MyReference.FirstDir)))</LibDir>
                <SecondDir>$(FirstDir)</SecondDir>
                <SecondDir Condition="$(LibDir) != 'lib'">$([System.IO.Directory]::GetParent(%(MyReference.FirstDir)))</SecondDir>
                <VersionDir>$([System.IO.Directory]::GetParent(%(MyReference.SecondDir)))</VersionDir>
                <PackageDir>$([System.IO.Directory]::GetParent(%(MyReference.VersionDir)))</PackageDir>
                <PackageID>$([System.IO.Path]::GetFileName(%(MyReference.PackageDir)))</PackageID>
                <NuspecFile>%(MyReference.VersionDir)\%(MyReference.PackageID).nuspec</NuspecFile>
            </MyReference>
    
            <!-- Get Non nuget references -->
            <ExcludedReference Condition="@(MyReference) != ''" Include="@(MyReference -> '%(PackageID)')" Exclude="@(PackageReference -> '%(identity)')"/>
    
            <!-- Get nuget references after exclude non-nuget references -->
            <MyPackage Condition="@(MyReference) != ''" Include="@(MyReference -> '%(PackageID)')" Exclude="@(ExcludedReference -> '%(identity)')"/>
    
            <!-- Read Nuspec File -->
            <NuspecContent Condition="@(NuspecFile) != ''" Include="%(MyPackage.NuspecFile)">
                <Content>$([System.IO.File]::ReadAllText(%(fullpath)))</Content>
                <PackageIDRegex>&lt;id&gt;(.*?)&lt;/id&gt;</PackageIDRegex>
                <PackageID>$([System.Text.RegularExpressions.Regex]::Match('%(NuspecContent.Content)', '%(NuspecContent.PackageIDRegex)' ))</PackageID>
                <PackageID>$([System.String]::Copy('%(NuspecContent.PackageID)').Replace('&lt;id&gt;','').Replace('&lt;/id&gt;', '').Trim())</PackageID>
    
                <VersionRegex>&lt;version&gt;(.*?)&lt;/version&gt;</VersionRegex>
                <Version>$([System.Text.RegularExpressions.Regex]::Match('%(NuspecContent.Content)', '%(NuspecContent.VersionRegex)' ))</Version>
                <Version>$([System.String]::Copy('%(NuspecContent.Version)').Replace('&lt;version&gt;','').Replace('&lt;/version&gt;', '').Trim())</Version>
            </NuspecContent>
        </ItemGroup>
    
        <!-- Generate Nuspec File with dependencies-->
        <PropertyGroup>
            <NewLine>%0d%0a</NewLine>
            <Space2>%20%20</Space2>
            <Space4>$(Space2)$(Space2)</Space4>
            <Space6>$(Space4)$(Space2)</Space6>
            <HasDependencies>false</HasDependencies>
            <HasDependencies Condition="@(MyPackage) != ''">true</HasDependencies>
    
            <Nuspec>$(Nuspec)&lt;?xml version="1.0" encoding="utf-8"?&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)&lt;package &gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space2)&lt;metadata&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;id&gt;$id$&lt;/id&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;version&gt;$version$&lt;/version&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;title&gt;$title$&lt;/title&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;authors&gt;$author$&lt;/authors&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;owners&gt;$author$&lt;/owners&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;requireLicenseAcceptance&gt;false&lt;/requireLicenseAcceptance&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;licenseUrl&gt;https://Your Company.com&lt;/licenseUrl&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;icon&gt;PackageIcon.png&lt;/icon&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;projectUrl&gt;http://Your Company.com&lt;/projectUrl&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;description&gt;$description$&lt;/description&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;releaseNotes&gt;$description$&lt;/releaseNotes&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;copyright&gt;$copyright$&lt;/copyright&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;tags&gt;$title$ Your Company Your Company.com&lt;/tags&gt;$(NewLine)</Nuspec>
    
            <Nuspec>$(Nuspec)$(Space4)&lt;dependencies&gt;$(NewLine)</Nuspec>
            <Nuspec Condition="$(HasDependencies)">$(Nuspec)$(Space6)&lt;group targetFramework=&quot;.NETFramework4.8&quot;&gt;$(NewLine)</Nuspec>
            <Nuspec Condition="$(HasDependencies)">$(Nuspec)@(NuspecContent -> '$(Space6)&lt;dependency id=&quot;%(PackageID)&quot; version=&quot;%(Version)&quot;/&gt;', '$(NewLine)')$(NewLine)</Nuspec>
            <Nuspec Condition="$(HasDependencies)">$(Nuspec)$(Space6)&lt;/group&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;/dependencies&gt;$(NewLine)</Nuspec>
    
            <Nuspec>$(Nuspec)$(Space2)&lt;/metadata&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space2)&lt;files&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space4)&lt;file src="..\**\PackageIcon.png"/&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)$(Space2)&lt;/files&gt;$(NewLine)</Nuspec>
            <Nuspec>$(Nuspec)&lt;/package&gt;$(NewLine)</Nuspec>
        </PropertyGroup>        
        <WriteLinesToFile Lines="$(Nuspec)" File="$(MSBuildProjectDirectory)\$(MSBuildProjectName).nuspec" Overwrite="true"/>
    </Target>
    

    The generated .nuspec file will be:

    <?xml version="1.0" encoding="utf-8"?>
    <package >
      <metadata>
        <id>$id$</id>
        <version>$version$</version>
        <title>$title$</title>
        <authors>$author$</authors>
        <owners>$author$</owners>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <licenseUrl>https://company.com</licenseUrl>
        <icon>PackageIcon.png</icon>
        <projectUrl>http://company.com</projectUrl>
        <description>$description$</description>
        <releaseNotes>$description$</releaseNotes>
        <copyright>$copyright$</copyright>
        <tags>$title$ company company.com</tags>
        <dependencies>
          <dependency id="Company.Globals" version="2022.7.19"/>
          <dependency id="Portable.BouncyCastle" version="1.9.0"/>
        </dependencies>
      </metadata>
      <files>
        <file src="..\**\PackageIcon.png"/>
      </files>
    </package>
    

    Its really to hard to achieve anything with MSBuild. Maybe you can create a console app for that and just call console app directly with <Exec Command =""/>. But current approach is friendly with MSBuild. Its not 100% organic approach. MSBuild should provide us a target to get a referenced nuget packages with its referenced version.