3. App 배포하기
다른 개발자를 위해 Class Library를 만들거나 일반적인 사용자를 위한 App을 만들게 되는 경우 이를 사용 가능하도록 하려면 배포하는 과정이 필요하며 배포하는 방식은 다음과 같이 크게 2가지가 있습니다.
- Self-contained
- Framework-dependent
Framework-dependent는 배포해야 하는 파일 집합의 크기가 매우 간소해지지만 시스템이 .NET이 반드시 설치되어 있어야 합니다. 반면 Self-contained는 수많은 파일들이 함께 배포가 되는 경우이므로 아주 간단한 Console App정도라 하더라도 매우 많은 수의 파일이 동반될 수 있고 그에 따라 배포되어야 하는 용량도 매우 커질 수 있습니다. 하지만 Self-contained는 .NET의 설치 여부와는 관련 없이 실행이 보장될 수 있습니다.
배포 방식을 확인해 보기 위해 Visual Studio (Code가 아닌)에서 Console App프로젝트를 생성합니다.(최신의 .NET SDK를 기본으로 사용합니다.)
프로젝트가 생성되면 생성된 프로젝트의 Program.cs를 다음과 같이 수정합니다.
Console.WriteLine("프로그램 실행 중...");
Console.WriteLine($"실행중인 OS의 버전 : {Environment.OSVersion}.");
if (OperatingSystem.IsMacOS())
Console.WriteLine("MacOS에서 실행");
else if (OperatingSystem.IsWindowsVersionAtLeast(major: 10))
Console.WriteLine("Windows에서 실행");
else
Console.WriteLine("기타 OS에서 실행");
위와 같이 수정된 상태에서 App을 실행하면 현재 운영체제의 버전과 실행되고 있는 운영체제의 종류를 표시하게 됩니다.
그리고 프로젝트의 csproj파일을 열어 다음과 같이 <RuntimeIdentifiers>를 추가합니다.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RuntimeIdentifiers>
win10-x64;osx-x64;osx.11.0-arm64;linux-x64;linux-arm64
</RuntimeIdentifiers>
</PropertyGroup>
win10-x64는 Windows 10이나 Windows Server 2016 64bit 버전을 의미합니다. 만약 Microsoft Surface Pro X와 같은 ARM환경이라면 win10-arm64를 지정해 줄 수 있습니다.
osx-x64는 macOS Sierra 10.12이나 그 이후 버전의 macOS를 의미합니다. 만약 Catalina와 같은 특정 버전만을 지정하려면 osx.10.15-x64처럼 명시해 줍니다.
linux-x64는 Ubuntu나 CentOS, Debian, Fedora와 같은 데스크톱 배포판의 Linux를 의미합니다. 만약 32bit Raspberry Pi에서 실행되는 경우라면 linux-arm으로 지정해 줄 수 있고 64bit라면 linux-arm64가 될 수 있습니다.
위와 같은 설정은 해당 App이 실행될 OS를 지정해 주는 것이며 이러한 설정에 따라서도 배포되는 파일이 달라질 수 있습니다.
Visual Studio의 프로젝트에서 마우스 오른쪽 버튼을 눌러 배포로 들어간 다음 'Folder'를 선택합니다.
배포할 위치를 설정하고
배포를 시도하면 해당 폴더에 App을 배포할 수 있는 파일이 생성됩니다.
배포 설정 중 Deployment mode가 있는데 여기서 Framework-dependent의 설정을
Self-contained로 변경합니다.
그리고 다시 배포를 시도하면 이번에는 더 많은 파일이 배포를 위해 생성되어 있음을 확인할 수 있습니다.
● 배포를 위한 dotnet 명령줄 도구 사용하기
.NET SDK를 설치하게 되면 dotnet이라는 명령줄 도구도 같이 설치됩니다. 이 명령줄 도구는 프로젝트 생성부터 시작해 프로젝트 전반에 사용될 수 있는 다양한 명령어들을 포함하고 있습니다.
dotnet명령을 사용하기 위해 Terminal을 열고 작업할 폴더로 이동합니다. Terminal을 실행하였으면 아래 명령을 통해 현재 설치된 생성 가능한 프로젝트 템플릿을 확인합니다.
dotnet new --list (혹은 dotnet new -l) |
현재 운영체제의 정보와 함께 어떤 .NET SDK와 runtime이 설치되어 있는지는 '--info'로 확인할 수 있습니다.
기타 그 밖에 dotnet은 현재 폴더에 위치하고 있는 프로젝트의 관리를 위해서 아래와 같은 몇 가지 명령을 사용할 수 있습니다. (모든 명령은 dotnet이 우선되어야 합니다.)
restore | 현재 프로젝트의 의존성을 확인하고 다운로드하도록 합니다. |
build | 현재 프로젝트를 빌드(컴파일)합니다. |
test | 프로젝트를 위한 Unit 테스트를 빌드하고 실행합니다. |
run | 현재 프로젝트를 빌드하고 실행합니다. |
pack | 현재 프로젝트의 NuGet 패키지를 생성합니다. |
publish | 현재 프로젝트를 빌드하고 배포합니다. 이때 배포의 형태는 dependent나 self-contained가 될 수 있습니다. |
add | 필요한 패키지나 다른 프로젝트를 참조추가합니다. |
remove | 참조된 패키지나 다른 프로젝트를 참조추가에서 제거합니다. |
list | 프로젝트에 참조된 패키지나 프로젝트의 리스트를 나열합니다. |
위 명령에서 예를 들어 특정 app을 배포하려면 다음과 같이 할 수 있습니다.
dotnet publish -c Release -r win10-x64 |
이 명령은 현재 프로젝트를 Windows10 64bit에서 실행되는 App을 Release로 빌드하여 self-contained로 배포 파일을 생성하도록 합니다.
위와 같은 명령에 따라 Windows가 아닌 Linux와 같은 다른 플랫폼에서의 배포가 실행되는 경우라면 'linux-x64'를 대신 지정해 주면 됩니다.
dotnet publish -c Release -r linux-x64 |
위와 같은 방식은 이전에도 언급하였지만 배포된 모든 파일을 Target시스템에 그대로 복사하는 경우 그 실행을 보장할 수 있다는 장점이 있습니다. 그러나 단 몇 kb밖에 되지 않는 Console App실행을 위해 수십에서 심지어 100MB 용량이 넘는 파일들을 같이 배포해야 한다는 단점 또한 존재하므로 상황에 따라 적절한 배포 방법이 선택되어야 합니다.
만약 App이 실행되는 플렛폼에 .NET이 설치되어 있다는 것을 보장할 수 있다면 App을 SingleFile로 배포할 수도 있습니다.
dotnet publish -r win10-x64 -c Release --self-contained=false /p:PublishSingleFile=true |
SingleFile배포는 말 그대로 하나의 단일 파일로 App을 배포한다는 것을 뜻하며 실제 위 명령으로 App의 배포본을 생성하는 경우 실행 가능한 형태의 exe와 pdb파일, 이렇게 2개의 파일만 생성됨을 알 수 있습니다.
참고로 pdb는 App의 디버깅을 위한 파일로 App이 예외를 던지게 되는 경우 Debugging에 필요한 정보를 제공해 주는 파일인데 exe는 pdb 없이도 실행이 가능합니다. 실행파일의 크기가 약간 더 커질 수 있다는 점을 감안할 수 있다면 프로젝트의 csproj파일에 아래와 같이 'DebugType'을 추가하여 pdb에 있는 디버깅 정보까지 포함된 SingleFile 배포할 수 있습니다.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DebugType>embedded</DebugType>
</PropertyGroup>
물론 실행하고자 하는 플렛폼에서 .NET이 설치되어 있음을 보장할 수 없는 경우라도 하더라도 단일 파일로의 배포가 불가능하지는 않습니다.
dotnet publish -r win10-x64 -c Release /p:PublishSingleFile=true |
위 명령은 이전 명령에서 '--self-contained=false'만을 제외한 것인데 이 방식은 App에 필요한 모든 요소를 exe나 dll안에 포함 시키게 되므로
단일 파일의 용량이 그만큼 증가할 수 있음에 유의해야 합니다.
위와 같은 배포 형태나 self-contained의 문제는 배포되는 크기가 너무 커진다는 것에 있습니다. 특히 브라우저에 필요한 모든 Library가 다운로드되어야 하는 Blazor WebAssembly components가 이런 상황에 매우 부담될 수 있습니다.
이에 대한 절충안으로 나온 방법이 프로젝트에서 필요로 하지 않는 Assembly만을 제외하여 배포본으로 패킹하는 것입니다. .NET 3.0에서 'app trimming'이라는 이름으로 알려진 이 방법은 소스코드이 분석을 통해 필요한 Assembly와 그렇지 않은 Assembly를 식별합니다.
.NET 5에 와서 'app trimming'은 한 단계 더 발전하여 Assembly내부에 존재하는 Type이나 Member 중 사용되지 않는 것들을 식별하여 제거함으로써 자체적인 Assembly용량마저 줄일 수 있게 되었습니다. 다만 .NET 5가 출시될 당시에 이 기술은 실험적으로만 사용될 수 있는 단계였기에 기본적으로 해당 기능은 비활성 상태였습니다.
.NET 6에서 마이크로소프트는 그들의 Library에 Annotation을 추가하여 좀 더 안전하게 Type과 Member가 좀더 안전하게 trimming이 가능하게 되었으며 이것을 'link trim mode'이라고 합니다.
점차 개선되는 trimming기술은 사용되지 않는 Type, Member, Assmebly들을 좀 더 잘 드러내도록 하였는데 비록 reflection과 같은 동적 코드의 경우 정확성이 떨어질 수 있지만 해당 문제점 또한 상세 설정을 통해 제어할 수 있습니다.
trimming은 가장 크게 Assembly를 대상으로 적용하는 경우가 있는데 이를 위한 설정 방법은 약 2가지가 존재하며 그중 하나는 프로젝트의 csproj파일에 PublishTrimmed를 추가하는 것과
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
다른 하나는 dotnet명령줄 도구 사용 시 '-p:PublishTrimmed=True'를 사용하는 것입니다.
만약 trimming을 Type이나 Member에 적용하려면 TrimMode를 추가로 설정합니다.
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>Link</TrimMode>
p:PublishTrimmed=True -p:TrimMode=Link |
다만 .NET 6에서 Link TrimMode는 기본으로 적용되므로 TrimMode를 사용하지 않을 경우에만 Assembly의 Trimming을 의미하는 'copyused'와 같은 값을 설정합니다.
4. NuGet 배포
NuGet은 수많은 개발자에 의해 만들어진 무수히 많은 패키지들을 제공하고 있고 소프트웨어 개발단계에서 필요한 패키지를 내려받아 사용할 수 있습니다. 예를 들어 json을 다루고자 하는 경우 가장 유명한 패키지로 Newtonsoft.Json이 있고 Visual Studio안에서는 NuGet Package Manager를 사용하거나
Visual Studio Code라면 'dotnet add package'명령을 통해 필요한 패키지를 프로젝트에 참조 추가할 수 있습니다.
그리고 추가된 패키지는 프로젝트의 csproj파일을 통해 확인할 수 있습니다.
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
● 의존성 고정
패키지를 지속적으로 복구하고 신뢰성 있는 코드를 작성하는 데 있어서 의존성을 고정하는 것은 중요한 문제이며 이는 특정 .NET버전을 위해 릴리즈 된 동일한 패키지의 제품군을 사용한다는 것을 의미합니다. 예를 들어 'SQLite for .NET 6.0'의 경우처럼
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
</ItemGroup>
의존성을 고정하기 위해서 모든 패키지는 betas (beta1), release candidates (rc4), wildcards (*)를 포함한 추가 한정자가 없는 단일 버전을 가져야 합니다.
특히 와일드카드(wildcards)는 가장 최신의 릴리즈 버전을 의미하므로 향후 업데이트된 버전이 자동으로 참조되고 사용될 수 있도록 하지만 최신의 버전은 이미 만들어진 코드를 수정하도록 요구하는 경우가 있으므로 프로그램이 예외를 일으킬 위험성을 안고 있습니다.
일반적으로 Package를 참조하는 경우 특정 버전을 지정하지 않으면 가장 최신의 버전이 사용되지만 설정을 복사하는 경우 의도치 않게 와일드카드 설정이나 기타 다른 한정자를 같이 복사할 수 있으므로 이를 염두에 두어야 합니다.
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-preview.*" />
● NuGet을 위한 Library 패키징
이전 예제 프로젝트에서 mylibrary라는 이름으로 프로젝트를 생성한 뒤 정규식을 통해 문자열에서 숫자만 추출하는 작은 메서드를 만들어 보았습니다.
using System.Text.RegularExpressions;
namespace mylibrary;
public static class MyStringExtension
{
public static int OnlyNumber(this string sValue)
{
return int.Parse(Regex.Replace(sValue, @"\D", string.Empty));
}
}
만약 위와 같이 만들어진 Library를 NuGet을 통해 배포하고자 한다면 우선 프로젝트의 csproj파일을 아래와 같이 수정해야 합니다.
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>cliel.dotnet.library.package</PackageId>
<PackageVersion>1.0.0.0</PackageVersion>
<Title>.NET 6 numerical extraction</Title>
<Authors>cliel</Authors>
<PackageLicenseExpression>MS-PL</PackageLicenseExpression>
<PackageProjectUrl>
http://cliel.com/
</PackageProjectUrl>
<PackageIcon>cliel.png</PackageIcon>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>first release</PackageReleaseNotes>
<Description>The extension methods to extraction from a string value.</Description>
<Copyright>Copyright © 2022 cliel Limited</Copyright>
<PackageTags>dotnet string extraction</PackageTags>
</PropertyGroup>
<ItemGroup>
<None Include="cliel.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
특히 위에서 PackageId는 패키지를 구분하는 값에 해당하므로 전 세계에서 유일한 값이어야 하며 PackageLicenseExpression에서는 임의의 라인선스를 지정하거나
SPDX License List | Software Package Data Exchange (SPDX)
위 규격을 따르는 License를 지정할 수 있습니다. 또한 PackageIcon에서 지정된 파일은 프로젝트 폴더에 위치하면 됩니다.
위와 같이 설정값을 저장한 후 프로젝트를 빌드합니다. Visual Studio를 사용하는 경우라면 Toolbar의 Build메뉴를 사용하고 Visual Studio Code나 기타 다른 편집기를 사용한다면 Terminal에서 'dotnet build -c release'명령을 사용합니다.
위 설정에서 <GeneratePackageOnBuild>가 true로 설정되어 있는데 이 설정은 프로젝트를 빌드하면 자동으로 Package가 생성될 수 있도록 하는 것입니다. 만약 위 설정을 적용하지 않고 수동으로 Package를 생성하고자 한다면 Visual Studio에서는 Build -> Pack ... 메뉴를 사용하고 Terminal에서 명령을 사용해야 한다면 'dotnet pack -c Release'를 사용합니다.
● NuGet feed로 Package배포
만들어진 Package를 다른 사람이 내려받아 사용하게 하려면 해당 Package를 'NuGet feed'로 업로드해야 합니다. 이를 위해 우선 웹브라우저를 열고 아래 주소로 이동합니다.
로그인을 위해서는 마이크로소프트 계정이 필요하므로 계정이 없다면 별도로 생성해야 합니다. 예제에서는 이미 계정을 가지고 있다는 것을 가정하고 진행할 것입니다.
해당 화면에서 'Browse...'를 선택하고 위에서 만든 unpkg파일을 선택합니다. 그리고 설정한 정보를 확인한 후 정보가 올바르게 등록되었으면 하단의 Submit버튼을 눌러줍니다.
업로드 후 일정 시간이 지난 후에 NuGet에서 Package를 검색하고 내려받을 수 있습니다.
'.NET' 카테고리의 다른 글
[.NET] 닷넷 - 5. .NET Framework에서 .NET으로의 전환 (0) | 2022.06.24 |
---|---|
[.NET] 닷넷 - 4. Decompiling (0) | 2022.06.24 |
[.NET] 닷넷 - 2. .NET components (0) | 2022.06.24 |
[.NET] 닷넷 - 1. .NET 6 개요 (0) | 2022.06.24 |
[ASP.NET Core] IIS 배포 (게시) (0) | 2021.12.15 |