NuGet 패키지에서 프로젝트 출력 디렉토리로 기본 파일 추가


126

네이티브 win32 dll을 pinvoke하는 .Net 어셈블리 용 NuGet 패키지를 만들려고합니다. 어셈블리와 네이티브 dll을 모두 프로젝트 참조에 추가 한 어셈블리와 함께 포장해야하며 (이 부분에서는 문제 없음) 네이티브 dll은 프로젝트 출력 디렉토리 또는 다른 상대 디렉토리에 복사해야합니다.

내 질문은 :

  1. Visual Studio가 참조 목록에 추가하지 않고 네이티브 dll을 어떻게 포장합니까?
  2. 기본 dll을 복사하려면 install.ps1을 작성해야합니까? 그렇다면 패키지 내용을 복사하기 위해 어떻게 액세스 할 수 있습니까?

1
런타임 / 아키텍처 특정 라이브러리에 대한 지원이 있지만 기능 문서가 부족하고 UWP에 특정한 것 같습니다. docs.microsoft.com/en-us/nuget/create-packages/…
Wouter

답변:


131

Copy대상 파일 의 대상을 사용하여 필요한 라이브러리를 복사하면 해당 파일을 프로젝트를 참조하는 다른 프로젝트로 복사하지 않으므로 결과는 DllNotFoundException입니다. 그러나 NoneMSBuild는 모든 None파일을 참조하는 프로젝트에 복사 하므로 요소를 사용하여 훨씬 간단한 대상 파일로 수행 할 수 있습니다 .

<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>

build필요한 기본 라이브러리와 함께 nuget 패키지 의 디렉토리에 대상 파일을 추가하십시오 . 대상 파일은 dll디렉토리의 모든 하위 디렉토리에있는 모든 파일을 포함합니다 build. 따라서 관리되는 어셈블리에서 사용하는 기본 라이브러리의 버전 x86x64버전 을 추가 Any CPU하려면 다음과 유사한 디렉토리 구조가 생깁니다.

  • 짓다
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

같은 x86x64내장 할 때 디렉토리는 프로젝트의 출력 디렉토리에 생성됩니다. 하위 디렉토리가 필요하지 않은 경우 **및 디렉토리를 %(RecursiveDir)제거하고 build디렉토리에 필요한 파일을 직접 포함시킬 수 있습니다 . 다른 필수 컨텐츠 파일도 같은 방식으로 추가 할 수 있습니다.

NoneVisual Studio에서 열 때 대상 파일 과 같이 추가 된 파일은 프로젝트에 표시되지 않습니다. Contentnupkg 에서 폴더를 사용하지 않는 이유가 궁금한 경우 powershell 스크립트를 사용하지 않고CopyToOutputDirectory 요소 를 설정할 수있는 방법이 없기 때문입니다 (명령 프롬프트가 아닌 Visual Studio 내에서 빌드 서버 또는 서버에서 실행됩니다) 다른 IDE를 사용하고 project.json / xproj DNX 프로젝트에서 지원되지 않음 ) 프로젝트 에서 파일의 추가 사본 을 사용하는 대신 파일에 a 를 사용하는 것을 선호합니다 .Link

업데이트 : 비록이해야 또한 작업 Content보다는 None이 파일이 한 단계가 제거보다 더 참조하는 프로젝트에 복사되지 않도록 MSBuild에서에서 문제가 있음을 표시 (예를 들어으로 Proj1 -> proj2 -> proj3, proj3는 파일을받지 않습니다 proj1의 NuGet 패키지에서 있지만 proj2는 것입니다.


4
선생님, 천재입니다! 매력처럼 작동합니다. 감사.
MoonStom

조건이 왜 필요한지 궁금 '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')하십니까? 나는 MSBuildThisFileDirectory항상 설정되어 있다고 생각했다 . 그때가 아닐까요?
kkm

@kkm 솔직히. 나는 그것이 필요하다고 생각하지 않습니다. 처음부터 어디에서 왔는지 기억조차 나지 않습니다.
kjbartel

@kkm 원래 System.Data.SQLite nuget 패키지를 수정했으며 포함 된 다른 모든 쓰레기를 제거했을 때 남겨둔 것처럼 보입니다. 원본 대상 파일 .
kjbartel

2
@SuperJMN 와일드 카드가 있습니다. 당신은 눈치 채지 못했습니까 **\*.dll? .dll모든 디렉토리의 모든 파일을 복사합니다 . **\*.*전체 디렉토리 트리를 쉽게 복사 할 수 있습니다 .
kjbartel

30

최근에는 관리되는 어셈블리와 관리되지 않는 공유 라이브러리 ( x86하위 디렉토리 에도 있어야 함)를 포함하여 EmguCV NuGet 패키지를 빌드하려고 할 때 동일한 문제가 발생했습니다.이 라이브러리 는 각 빌드 후에 빌드 출력 디렉토리에 자동으로 복사되어야했습니다 .

다음은 NuGet 및 MSBuild에만 의존하는 솔루션입니다.

  1. 관리되는 어셈블리를 /lib패키지 디렉토리 (명확한 부분)와 관리되지 않는 공유 라이브러리 및 관련 파일 (예 : .pdb 패키지)에 /build하위 디렉토리 ( NuGet docs에 설명 된대로)에 배치하십시오 .

  2. 모든 관리되지 않는 *.dll파일 끝의 이름을 다른 것으로 바꾸십시오 ( 예 : *.dl_NuGet이 주장 된 어셈블리가 잘못된 위치에 배치되는 것에 대해 신음하지 못하도록 방지하십시오 ( "문제 : lib 폴더 외부의 어셈블리" )).

  3. 다음 내용과 같이 서브 디렉토리에 사용자 정의 <PackageName>.targets파일을 추가 /build하십시오 (설명은 아래 참조).

    <?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>
    

위의 .targets파일은 대상 프로젝트 파일에 NuGet 패키지를 설치할 때 주입되며 기본 라이브러리를 출력 디렉토리에 복사합니다.

  • <AvailableItemName Include="NativeBinary" /> 프로젝트에 대한 새 항목 "빌드 조치"를 추가합니다 (Visual Studio의 "빌드 조치"드롭 다운에서도 사용 가능).

  • <NativeBinary Include=".../build/x86현재 프로젝트에 배치 된 기본 라이브러리를 추가하고 해당 파일을 출력 디렉토리에 복사하는 사용자 정의 대상에 액세스 할 수 있도록합니다.

  • <TargetPath>x86</TargetPath>파일에 사용자 정의 메타 데이터를 추가하고 기본 파일을 x86실제 출력 디렉토리 의 서브 디렉토리에 복사하도록 사용자 정의 대상에 지시합니다 .

  • <PrepareForRunDependsOn ...블록은 빌드가 의존하는 대상 목록에 사용자 지정 대상을 추가합니다 . 자세한 내용은 Microsoft.Common.targets 파일을 참조하십시오.

  • 사용자 정의 대상 CopyNativeBinaries에는 두 개의 복사 작업이 있습니다. 첫 번째 *.dl_파일은 확장명을 원래대로 변경하면서 파일을 출력 디렉토리로 복사하는 역할 을합니다 *.dll. 두 번째는 단순히 나머지 *.pdb파일 (예 : 파일)을 동일한 위치에 복사합니다 . 이것은 단일 복사 작업과 패키지 설치 중에 모든 파일의 이름을 바꾸어야하는 install.ps1 스크립트 로 대체 될 수 있습니다 .*.dl_*.dll

그러나이 솔루션은 여전히 ​​기본 바이너리를 NuGet 패키지가 포함 된 프로젝트를 참조하는 다른 프로젝트의 출력 디렉토리에 복사하지 않습니다. "최종"프로젝트에서도 NuGet 패키지를 참조해야합니다.


4
" 그러나이 솔루션은 여전히 ​​기본 바이너리를 NuGet 패키지가 처음 포함 된 프로젝트를 참조하는 다른 프로젝트의 출력 디렉토리로 복사하지 않습니다. 여전히"최종 "프로젝트에서 NuGet 패키지를 참조해야합니다. " 나를 위해 스토퍼를 보여주십시오. 그것은 일반적으로 너겟 패키지를 여러 프로젝트 (예 : 단위 테스트)에 추가해야한다는 것을 의미합니다 DllNotFoundException. 그렇지 않으면 던져집니다.
kjbartel

2
경고 때문에 파일의 이름을 바꾸는 것은 과감합니다.

<NoWarn>NU5100</NoWarn>프로젝트 파일 에 추가하여 경고를 제거 할 수 있습니다
Florian Koch

28

다음은 속성 을 사용하여 프로젝트에 기본 DLL을 다음 속성 .targets으로 주입 하는 대안입니다 .

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

이 기술의 주요 이점은 기본 DLL이 종속 프로젝트bin/ 폴더에 전 이적 으로 복사된다는 것 입니다.

.nuspec파일 의 레이아웃을보십시오 :

NuGet 패키지 탐색기의 화면 캡처

.targets파일 은 다음과 같습니다 .

<?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>

MyNativeLib.dll원본 프로젝트의 일부인 것처럼 삽입 하지만 흥미롭게도 파일은 Visual Studio에 표시되지 않습니다.

폴더 <Link>에서 대상 파일 이름을 설정 하는 요소를 확인하십시오 bin/.


Azure 서비스의 일부로 포함해야하는 일부 .bat 및 .ps1 파일로 처리합니다.-고맙습니다 :)
Zhaph-Ben Duguid

"(하지만 흥미롭게도 파일은 Visual Studio에서 보이지 않습니다." — 프로젝트 파일은 VS 자체 AFAIK에 의해 구문 분석되므로 외부 .target 파일에 추가 된 항목 (또는 대상 실행시 동적으로 생성 된 항목)은 표시되지 않습니다.
kkm

에서 로 변경하는 것 이외 의 다른 이전 답변과 다른 점은 무엇입니까? ContentNone
kjbartel

3
와우 빨리. 어쨌든, 그렇게하기로 결정했다면 적어도 '이것이 내 대답과 어떻게 다른지'를 물을 수 있습니다. 그 imo는 원래 질문을 편집하고 스스로 대답하고 다른 사람들의 의견으로 답변을 홍보하는 것보다 공정합니다. 개인적으로이 답변이 당신보다 더 낫다는 점은 말할 것도 없습니다. 간결하고 요점은 읽기 쉽습니다.
Maksim Satsikau

3
@MaksimSatsikau 역사를보고 싶을 수도 있습니다. 질문을보다 명확하게하기 위해 편집 한 다음 질문에 대답했습니다. 이 답변은 몇 주 후에 왔으며 사실상 사본이었습니다. 내가 무례한 것을 발견하면 미안
kjbartel 2016 년

19

다른 사람이 이것을 우연히 발견하면.

.targets파일 이름 MUST NuGet 패키지 아이디와 동일

다른 것은 작동하지 않습니다.

크레딧은 https://sushihangover.github.io/nuget-and-msbuild-targets/ 로 이동 하십시오.

나는 실제로 여기에 언급 된대로 더 자세히 읽었어야합니다. 나이가 들었어요 ..

맞춤 설정 추가 <PackageName>.targets


3
당신은 내 하루 종일 구해!
zheng yu

1
다른 문제로 일주일 동안 문제를 해결했습니다. 당신과 그 github 페이지에 감사드립니다.
Glenn Watson

13

조금 늦었지만 그에 대한 nuget 패키지 exaclty를 만들었습니다.

아이디어는 너겟 패키지에 추가 특수 폴더를 두는 것입니다. Lib 및 Content를 이미 알고 있다고 확신합니다. 내가 만든 너겟 패키지는 Output이라는 폴더를 찾고 거기에있는 모든 것을 프로젝트 출력 폴더에 복사합니다.

패키지에 너겟 의존성을 추가하는 것만하면됩니다. http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/에 .

나는 그것에 대해 블로그 게시물을 작성했습니다 : http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0


대단해! 그러나 이것은 현재 프로젝트에서만 작동합니다. 프로젝트가 "클래스 라이브러리"이고 예를 들어 "웹 애플리케이션"에 종속성으로 추가하려는 경우 웹 애플리케이션에서 DLL이 빌드되지 않습니다! 내 "빠른 해결"은 라이브러리에 NuGet을 만들고 클래스 라이브러리에 적용하고 종속성 (이 경우 DLL)에 대한 다른 Nuget을 만들어 WebApplication에 적용하는 것입니다. 가장 좋은 해결책은 무엇입니까?
Wagner Leonardi

.NET 4.0 (Windows) 전용으로이 프로젝트를 작성한 것 같습니다. 이식 가능한 클래스 라이브러리도 지원하도록 업데이트 할 계획입니까?
애니

1

사용하기 쉬운 순수한 C # 솔루션이 있으며 NuGet 제한 사항을 신경 쓸 필요가 없습니다. 다음과 같이하세요:

프로젝트에 기본 라이브러리를 포함하고 빌드 조치 특성을로 설정하십시오 Embedded Resource.

이 라이브러리를 PInvoke하는 클래스에 다음 코드를 붙여 넣습니다.

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());
    }
}

다음 UnpackNativeLibrary("win32");과 같이 정적 생성자에서이 메소드를 호출 하면 라이브러리가 필요할 때 바로 디스크에 압축 해제됩니다. 물론 디스크의 해당 부분에 대한 쓰기 권한이 있는지 확인해야합니다.


1

이것은 오래된 질문이지만 지금은 같은 문제가 있으며 약간 까다 롭지 만 매우 간단하고 효과적인 처리 방법을 찾았습니다. Nuget 표준 Content 폴더에서 각 구성마다 하나의 하위 폴더로 다음 구조를 만듭니다.

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

nuspec 파일을 압축하면 디버그 및 릴리스 폴더의 각 기본 라이브러리에 대해 다음과 같은 메시지가 나타납니다.

문제 : lib 폴더 외부에서 어셈블리. 설명 : 'Content \ Bin \ Debug \ ??????. dll'어셈블리는 'lib'폴더 안에 없으므로 패키지가 프로젝트에 설치 될 때 참조로 추가되지 않습니다. 해결 방법 : 참조해야하는 경우 'lib'폴더로 이동하십시오.

네이티브 라이브러리는 NET 어셈블리 참조로 추가되지 않는 것이 우리의 목표 일 뿐이므로 이러한 "솔루션"이 필요하지 않습니다.

장점은 다음과 같습니다.

  1. 패키지 제거시 재설정하기 어려운 이상한 효과가있는 성가신 스크립트가없는 간단한 솔루션입니다.
  2. Nuget은 설치 및 제거시 기본 라이브러리를 다른 컨텐츠로 관리합니다.

단점은 다음과 같습니다.

  1. 각 구성에 대한 폴더가 필요합니다 (그러나 일반적으로 두 가지만 있습니다 : 디버그 및 릴리스, 각 구성 폴더에 설치해야하는 다른 컨텐츠가있는 경우이 방법 중 하나입니다)
  2. 기본 라이브러리는 각 구성 폴더에 복제되어야합니다 (그러나 각 구성마다 다른 버전의 기본 라이브러리가있는 경우이 방법 중 하나임)
  3. 각 폴더의 각 고유 dll에 대한 경고 (단, 내가 말했듯이 경고는 VS 설치시 패키지 사용자가 아닌 팩 시간에 패키지 작성자에게 발행됩니다)

0

정확한 문제를 해결할 수는 없지만 제안 할 수 있습니다.

귀하의 핵심 요구 사항은 "참조를 자동 등록하지 마십시오"입니다.

따라서 "솔루션 항목"에 익숙해 져야합니다

여기를 참조하십시오 :

NuGet 패키지에 솔루션 레벨 항목 추가

네이티브 dll의 복사본을 집으로 가져 오기 위해 파워 쉘 부두를 작성해야합니다 (자동 추가 참조 부두를 실행하지 않기 때문에)

다음은 써드 파티 참조 폴더에 파일을 넣는 ps1 파일입니다.

처음부터 시작할 필요없이 네이티브 dll을 "홈"에 복사하는 방법을 알아낼 수 있습니다.

다시 말하지만, 직접 적중은 아니지만 아무것도 아닌 것보다 낫습니다.

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

컨텐츠 폴더입니다

nuget pack [projfile].csproj파일을 내용으로 표시하면 명령 이 자동으로 수행합니다.

그런 다음 ItemGroup & NativeLibs & None 요소를 추가하여 여기에 언급 된대로 프로젝트 파일을 편집하십시오.

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

나를 위해 일했다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.