Thêm tệp gốc từ gói NuGet vào thư mục đầu ra của dự án


126

Tôi đang cố gắng tạo gói NuGet cho một hội đồng .Net có khả năng kết nối với win32 dll riêng. Tôi cần phải đóng gói cả lắp ráp và dll gốc với lắp ráp được thêm vào các tham chiếu dự án (không có vấn đề gì ở phần này) và dll gốc nên được sao chép vào thư mục đầu ra dự án hoặc một số thư mục tương đối khác.

Câu hỏi của tôi là:

  1. Làm thế nào để tôi đóng gói dll bản địa mà không có studio trực quan cố gắng thêm nó vào danh sách tài liệu tham khảo?
  2. Tôi có phải viết install.ps1 để sao chép dll gốc không? Nếu vậy làm thế nào tôi có thể truy cập nội dung gói để sao chép nó?

1
Có hỗ trợ cho các thư viện cụ thể về thời gian chạy / kiến ​​trúc, nhưng tài liệu tính năng còn thiếu và dường như là dành riêng cho UWP. docs.microsoft.com/en-us/nuget/create-packages/ triệt
Wouter

Câu trả lời:


131

Sử dụng Copymục tiêu trong tệp mục tiêu để sao chép các thư viện cần thiết sẽ không sao chép các tệp đó sang các dự án khác tham chiếu dự án, dẫn đến a DllNotFoundException. Điều này có thể được thực hiện với một tệp mục tiêu đơn giản hơn nhiều, bằng cách sử dụng một Nonephần tử, vì MSBuild sẽ sao chép tất cả Nonecác tệp vào các dự án tham chiếu.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Thêm tệp mục tiêu vào buildthư mục của gói nuget cùng với các thư viện riêng được yêu cầu. Tệp mục tiêu sẽ bao gồm tất cả dllcác tệp trong tất cả các thư mục con của buildthư mục. Vì vậy, để thêm một x86x64phiên bản của một thư viện riêng được sử dụng bởi một Any CPUhội đồng được quản lý, bạn sẽ kết thúc với một cấu trúc thư mục tương tự như sau:

  • xây dựng
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.target
  • lib
    • net40
      • ManagedAssugging.dll

Tương tự x86và các x64thư mục sẽ được tạo trong thư mục đầu ra của dự án khi được xây dựng. Nếu bạn không cần thư mục con thì **%(RecursiveDir)có thể được gỡ bỏ và thay vào đó bao gồm các tập tin cần thiết trong buildthư mục trực tiếp. Các tệp nội dung cần thiết khác cũng có thể được thêm vào theo cách tương tự.

Các tệp được thêm vào như Nonetrong tệp mục tiêu sẽ không được hiển thị trong dự án khi mở trong Visual Studio. Nếu bạn thắc mắc tại sao tôi không sử dụng Contentthư mục trong nupkg thì đó là vì không có cách nào để thiết lập CopyToOutputDirectoryphần tử mà không sử dụng tập lệnh powershell (sẽ chỉ được chạy trong Visual Studio, không phải từ dấu nhắc lệnh, trên máy chủ xây dựng hoặc trong các IDE khác và không được hỗ trợ trong các dự án DNX của Project.json / xproj ) và tôi thích sử dụng một Linktệp hơn là có một bản sao bổ sung của các tệp trong dự án.

Cập nhật: Mặc dù điều này cũng sẽ hoạt động Contentthay vì Nonecó lỗi trong msbuild nên các tệp sẽ không được sao chép vào các dự án tham chiếu hơn một bước (ví dụ: proj1 -> proj2 -> proj3, proj3 sẽ không nhận được các tệp từ gói NuGet của proj1 nhưng proj2 sẽ).


4
Thưa bạn, bạn là một thiên tài! Hoạt động như một lá bùa. Cảm ơn.
MoonStom

Tự hỏi tại sao điều kiện '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')cần thiết? Tôi nghĩ rằng MSBuildThisFileDirectoryluôn luôn được thiết lập. Khi đó sẽ không phải là trường hợp?
kkm

@kkm Thành thật mà nói. Tôi không nghĩ rằng nó là cần thiết. Tôi thậm chí không thể nhớ lại nơi ban đầu tôi đã nhận nó.
kjbartel

@kkm Ban đầu tôi đã sửa đổi gói nuget System.Data.SQLite và có vẻ như tôi đã bỏ lại phía sau khi tôi loại bỏ tất cả các crap khác mà chúng bao gồm. Tập tin mục tiêu gốc .
kjbartel

2
@SuperJMN Có ký tự đại diện ở đó. Bạn không nhận thấy**\*.dll sao? Đó là sao chép tất cả .dllcác tập tin trong tất cả các thư mục. Bạn có thể dễ dàng làm **\*.*để sao chép toàn bộ cây thư mục.
kjbartel

30

Gần đây tôi đã gặp vấn đề tương tự khi tôi cố gắng xây dựng gói NuGet EmguCV bao gồm cả tập hợp được quản lý và các x86thư mục chia sẻ không được quản lý (cũng phải được đặt trong thư mục con) phải được sao chép tự động vào thư mục đầu ra của bản dựng sau mỗi bản dựng .

Đây là một giải pháp tôi đã đưa ra, chỉ dựa vào NuGet và MSBuild:

  1. Đặt các tập hợp được quản lý trong /libthư mục của gói (phần rõ ràng) và các thư viện chia sẻ không được quản lý và các tệp có liên quan (ví dụ: gói .pdb) trong /buildthư mục con (như được mô tả trong tài liệu NuGet ).

  2. Đổi tên tất cả các *.dllkết thúc tệp không được quản lý thành một cái gì đó khác, ví dụ *.dl_để ngăn NuGet rên rỉ về các hội đồng bị cáo buộc được đặt ở một vị trí sai ( "Vấn đề: Lắp ráp bên ngoài thư mục lib." ).

  3. Thêm một <PackageName>.targetstệp tùy chỉnh trong /buildthư mục con với nội dung như sau (xem bên dưới để biết mô tả):

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <ItemGroup>
        <AvailableItemName Include="NativeBinary" />
      </ItemGroup>
      <ItemGroup>
        <NativeBinary Include="$(MSBuildThisFileDirectory)x86\*">
          <TargetPath>x86</TargetPath>
        </NativeBinary>
      </ItemGroup>
      <PropertyGroup>
        <PrepareForRunDependsOn>
          $(PrepareForRunDependsOn);
          CopyNativeBinaries
        </PrepareForRunDependsOn>
      </PropertyGroup>
      <Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
              Condition="'%(Extension)'=='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
              Condition="'%(Extension)'!='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
      </Target>
    </Project>
    

Tệp trên .targetssẽ được thêm vào khi cài đặt gói NuGet trong tệp dự án đích và chịu trách nhiệm sao chép các thư viện gốc vào thư mục đầu ra.

  • <AvailableItemName Include="NativeBinary" /> thêm một mục mới "Build Action" cho dự án (cũng có sẵn trong phần thả xuống "Build Action" bên trong Visual Studio).

  • <NativeBinary Include="...thêm các thư viện riêng được đặt vào /build/x86dự án hiện tại và làm cho chúng có thể truy cập được vào mục tiêu tùy chỉnh sao chép các tệp đó vào thư mục đầu ra.

  • <TargetPath>x86</TargetPath>thêm siêu dữ liệu tùy chỉnh vào các tệp và báo cho mục tiêu tùy chỉnh sao chép các tệp gốc vào x86thư mục con của thư mục đầu ra thực tế.

  • Các <PrepareForRunDependsOn ... khối cho biết thêm mục tiêu tùy chỉnh vào danh sách các mục tiêu xây dựng phụ thuộc vào, xem Microsoft.Common.targets tập tin để biết chi tiết.

  • Mục tiêu tùy chỉnh CopyNativeBinaries, chứa hai tác vụ sao chép. Cái đầu tiên chịu trách nhiệm sao chép bất kỳ *.dl_tập tin nào vào thư mục đầu ra trong khi thay đổi phần mở rộng của chúng trở lại ban đầu *.dll. Cái thứ hai chỉ đơn giản là sao chép phần còn lại (ví dụ như bất kỳ *.pdbtệp nào ) vào cùng một vị trí. Điều này có thể được thay thế bằng một tác vụ sao chép duy nhất và tập lệnh install.ps1 phải đổi tên tất cả *.dl_các tệp thành*.dll trong khi cài đặt gói.

Tuy nhiên, giải pháp này vẫn không sao chép các nhị phân riêng vào thư mục đầu ra của dự án khác tham chiếu đến dự án ban đầu bao gồm gói NuGet. Bạn vẫn phải tham khảo gói NuGet trong dự án "cuối cùng" của mình.


4
" Tuy nhiên, giải pháp này vẫn không sao chép các nhị phân riêng vào thư mục đầu ra của dự án khác tham chiếu gói đầu tiên bao gồm gói NuGet. Bạn vẫn phải tham khảo gói NuGet trong dự án" cuối cùng "của mình. " Đây là một nút chặn cho tôi Điều này thường có nghĩa là bạn cần thêm gói nuget vào nhiều dự án (chẳng hạn như kiểm tra đơn vị) nếu không bạn sẽ DllNotFoundExceptionbị ném.
kjbartel

2
một chút quyết liệt để đổi tên các tập tin vv chỉ vì cảnh báo.

bạn có thể xóa cảnh báo bằng cách thêm <NoWarn>NU5100</NoWarn>vào tệp dự án của mình
Florian Koch

28

Đây là một thay thế sử dụng .targetsđể tiêm DLL gốc trong dự án với các thuộc tính sau.

  • Build action = = None
  • Copy to Output Directory = = Copy if newer

Lợi ích chính của kỹ thuật này là DLL gốc được sao chép vào bin/thư mục của các dự án phụ thuộc liên tục .

Xem bố cục của .nuspectệp:

Chụp màn hình của NuGet Gói Explorer

Đây là .targetstập tin:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <None Include="$(MSBuildThisFileDirectory)\..\MyNativeLib.dll">
            <Link>MyNativeLib.dll</Link>
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

Điều này chèn vào MyNativeLib.dllnhư thể nó là một phần của dự án ban đầu (nhưng điều kỳ lạ là tệp này không hiển thị trong Visual Studio).

Lưu ý <Link>phần tử đặt tên tệp đích trong bin/thư mục.


Làm việc với một số tệp .bat và .ps1 tôi cần đưa vào như một phần của dịch vụ Azure của mình - cảm ơn :)
Zhaph - Ben Duguid

Tập (nhưng thật kỳ lạ là tập tin không hiển thị trong Visual Studio). - các tệp dự án được phân tích cú pháp bởi chính AFAIK của VS, vì vậy các mục được thêm vào trong các tệp .target bên ngoài (hoặc các tệp được tạo động trong thực thi đích) không được hiển thị.
kkm

Làm thế nào là này bất kỳ khác nhau để các câu trả lời trước đó khác khác hơn là thay đổi từ Contenttới None?
kjbartel

3
wow bạn nhanh quá. dù sao đi nữa, nếu bạn chọn làm như vậy, ít nhất bạn có thể hỏi 'điều này khác với câu trả lời của tôi như thế nào'. rằng imo sẽ công bằng hơn so với việc chỉnh sửa câu hỏi ban đầu, tự trả lời và sau đó quảng bá câu trả lời của bạn trong các bình luận của người khác. không đề cập đến việc cá nhân tôi thích câu trả lời đặc biệt này tốt hơn câu trả lời của bạn - nó ngắn gọn, chính xác và dễ đọc hơn
Maksim Satsikau

3
@MaksimSatsikau Bạn có thể muốn nhìn vào lịch sử. Tôi chỉnh sửa câu hỏi để làm cho nó rõ ràng hơn, sau đó trả lời câu hỏi. Câu trả lời này đến một vài tuần sau đó và thực sự là một bản sao. Xin lỗi nếu tôi thấy điều đó thô lỗ.
kjbartel

19

Nếu ai khác vấp phải điều này.

Tên .targetstệp PHẢI bằng Id Gói của NuGet

Bất cứ điều gì khác sẽ không làm việc.

Tín dụng truy cập: https://sushihangover.github.io/nuget-and-msbuild-target/

Tôi nên đọc kỹ hơn vì nó thực sự được ghi chú ở đây. Đã cho tôi nhiều tuổi ..

Thêm một tùy chỉnh <PackageName>.targets


3
bạn tiết kiệm cả ngày của tôi!
zheng yu

1
Bạn đã sửa một vấn đề dài một tuần với một cái gì đó khác. Cảm ơn bạn và trang github đó.
Glenn Watson

13

Hơi muộn một chút nhưng tôi đã tạo ra một gói nuget exaclty cho việc đó.

Ý tưởng là có thêm một thư mục đặc biệt trong gói nuget của bạn. Tôi chắc rằng bạn đã biết Lib và Nội dung. Gói nuget mà tôi đã tạo sẽ tìm một Thư mục có tên Đầu ra và sẽ sao chép mọi thứ có trong thư mục đầu ra của dự án.

Điều duy nhất bạn phải làm là thêm một phụ thuộc nuget vào gói http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

Tôi đã viết một bài đăng trên blog về nó: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0


Thật tuyệt vời! Tuy nhiên, điều này chỉ hoạt động trong dự án hiện tại. Nếu dự án là "Thư viện lớp" và bạn muốn thêm phụ thuộc vào "Ứng dụng web", ví dụ, DLL sẽ không được xây dựng trong ứng dụng web! "Khắc phục nhanh" của tôi là: tạo NuGet cho thư viện của bạn và áp dụng cho Thư viện lớp và tạo một Nuget khác cho các phụ thuộc (dlls trong trường hợp này) và áp dụng cho WebApplication. Bất kỳ giải pháp tốt nhất cho điều này?
Wagner Leonardi

Có vẻ như bạn đã tạo dự án này chỉ cho .NET 4.0 (Windows). Bạn có kế hoạch để cập nhật nó để hỗ trợ các thư viện lớp di động không?
Ani

1

Có một giải pháp C # thuần túy mà tôi thấy khá dễ sử dụng và tôi không phải bận tâm đến những hạn chế của NuGet. Thực hiện theo các bước sau:

Bao gồm thư viện riêng trong dự án của bạn và đặt thuộc tính Build Action của nó thành Embedded Resource.

Dán đoạn mã sau vào lớp nơi bạn PInvoke thư viện này.

private static void UnpackNativeLibrary(string libraryName)
{
    var assembly = Assembly.GetExecutingAssembly();
    string resourceName = $"{assembly.GetName().Name}.{libraryName}.dll";

    using (var stream = assembly.GetManifestResourceStream(resourceName))
    using (var memoryStream = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0))
    {
        stream.CopyTo(memoryStream);
        File.WriteAllBytes($"{libraryName}.dll", memoryStream.ToArray());
    }
}

Gọi phương thức này từ hàm tạo tĩnh như sau UnpackNativeLibrary("win32");và nó sẽ giải nén thư viện vào đĩa ngay trước khi bạn cần. Tất nhiên, bạn cần chắc chắn rằng bạn có quyền ghi vào phần đó của đĩa.


1

Đây là một câu hỏi cũ, nhưng bây giờ tôi có cùng một vấn đề và tôi đã tìm thấy một cách quay vòng hơi khó nhưng rất đơn giản và hiệu quả: tạo trong thư mục Nội dung tiêu chuẩn Nuget với cấu trúc sau với một thư mục con cho mỗi cấu hình:

/Content
 /bin
   /Debug
      native libraries
   /Release
      native libraries

Khi bạn đóng gói tệp nuspec, bạn sẽ nhận được thông báo sau cho mỗi thư viện gốc trong các thư mục Gỡ lỗi và Phát hành:

Vấn đề: Hội ngoài thư mục lib. Mô tả: Tập hợp 'Nội dung \ Bin \ Gỡ lỗi \ ?????? dll' không nằm trong thư mục 'lib' và do đó nó sẽ không được thêm làm tham chiếu khi gói được cài đặt vào dự án. Giải pháp: Di chuyển nó vào thư mục 'lib' nếu cần tham chiếu.

Chúng tôi không cần "giải pháp" như vậy bởi vì đây chỉ là mục tiêu của chúng tôi: rằng các thư viện gốc không được thêm vào dưới dạng tài liệu tham khảo NET Assemblies.

Những ưu điểm là:

  1. Giải pháp đơn giản không có tập lệnh rườm rà với các hiệu ứng lạ khó đặt lại khi gỡ cài đặt gói.
  2. Nuget quản lý các thư viện riêng như mọi nội dung khác khi cài đặt và gỡ cài đặt.

Những nhược điểm là:

  1. Bạn cần một thư mục cho mỗi cấu hình (nhưng thường chỉ có hai: Gỡ lỗi và Phát hành và nếu bạn có nội dung khác phải được cài đặt trong mỗi thư mục cấu hình, đây là cách để đi)
  2. Các thư viện riêng phải được sao chép trong mỗi thư mục cấu hình (nhưng nếu bạn có các phiên bản khác nhau của các thư viện riêng cho từng cấu hình, thì đây là cách để đi)
  3. Các cảnh báo cho từng dll riêng trong mỗi thư mục (nhưng như tôi đã nói, chúng cảnh báo được đưa ra cho người tạo gói tại thời điểm đóng gói, không phải cho người dùng gói tại thời điểm cài đặt VS)

0

Tôi không thể giải quyết vấn đề chính xác của bạn, nhưng tôi có thể cho bạn một gợi ý.

Yêu cầu chính của bạn là: "Và có nó không tự động đăng ký tham chiếu" .....

Vì vậy, bạn sẽ phải làm quen với "mục giải pháp"

Xem tài liệu tham khảo tại đây:

Thêm các mục cấp giải pháp trong gói NuGet

Bạn sẽ phải viết một số voodoo powershell để có được bản sao của dll bản địa của bạn vào nhà của nó (một lần nữa, vì bạn KHÔNG muốn voodoo tham chiếu tự động thêm vào)

Đây là tệp ps1 tôi đã viết ..... để đặt tệp vào thư mục tham chiếu của bên thứ ba.

Có đủ để bạn tìm ra cách sao chép dll bản địa của bạn vào một số "nhà" ... mà không phải bắt đầu lại từ đầu.

Một lần nữa, nó không phải là một cú đánh trực tiếp, nhưng tốt hơn là không có gì.

param($installPath, $toolsPath, $package, $project)
if ($project -eq $null) {
$project = Get-Project
}

Write-Host "Start Init.ps1" 

<#
The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. These are also used when installing a package using the Install-Package command within the Package Manager Console. Package IDs may not contain any spaces or characters that are invalid in an URL.
#>
$separator = " "
$packageNameNoVersion = $package -split $separator | select -First 1

Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host "package:" "${package}"
<# Write-Host "project:" "${project}" #>
Write-Host "packageNameNoVersion:" "${packageNameNoVersion}"
Write-Host " "

<# Recursively look for a .sln file starting with the installPath #>
$parentFolder = (get-item $installPath)
do {
        $parentFolderFullName = $parentFolder.FullName

        $latest = Get-ChildItem -Path $parentFolderFullName -File -Filter *.sln | Select-Object -First 1
        if ($latest -ne $null) {
            $latestName = $latest.name
            Write-Host "${latestName}"
        }

        if ($latest -eq $null) {
            $parentFolder = $parentFolder.parent    
        }
}
while ($parentFolder -ne $null -and $latest -eq $null)
<# End recursive search for .sln file #>


if ( $parentFolder -ne $null -and $latest -ne $null )
{
    <# Create a base directory to store Solution-Level items #>
    $thirdPartyReferencesDirectory = $parentFolder.FullName + "\ThirdPartyReferences"

    if ((Test-Path -path $thirdPartyReferencesDirectory))
    {
        Write-Host "--This path already exists: $thirdPartyReferencesDirectory-------------------"
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesDirectory
    }

    <# Create a sub directory for only this package.  This allows a clean remove and recopy. #>
    $thirdPartyReferencesPackageDirectory = $thirdPartyReferencesDirectory + "\${packageNameNoVersion}"

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
        Write-Host "--Removing: $thirdPartyReferencesPackageDirectory-------------------"
        Remove-Item $thirdPartyReferencesPackageDirectory -Force -Recurse
    }

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesPackageDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesPackageDirectory
    }

    Write-Host "--Copying all files for package : $packageNameNoVersion-------------------"
    Copy-Item $installPath\*.* $thirdPartyReferencesPackageDirectory -recurse
}
else
{
        Write-Host "A current or parent folder with a .sln file could not be located."
}


Write-Host "End Init.ps1" 

-2

Đặt nó là thư mục nội dung

lệnh nuget pack [projfile].csprojsẽ tự động làm điều đó cho bạn nếu bạn sẽ đánh dấu các tệp là nội dung.

sau đó chỉnh sửa tệp dự án như được đề cập ở đây, thêm phần tử Itemgroup & NativeLibs & none

<ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

đã làm cho tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.