Directory.Build.props에 정의한 속성에 대해 Condition 제약으로 값을 변경하는 방법
지난 글을 통해,
(OneDrive, Dropbox 등의 공유 디렉터리에 있는) C# 프로젝트의 출력 경로 변경하기
; https://www.sysnet.pe.kr/2/0/13907
빌드 결과물을 프로젝트 경로의 하위가 아닌, 별도로 지정한 임시 디렉터리로 지정할 수 있었는데요, 아쉽게도 여기엔 문제가 좀 있습니다. ^^;
한 가지 사례로, Web Application 유형의 프로젝트라면 Visual Studio에서 F5 디버깅 시 기본적으로 ./[프로젝트]/bin 디렉터리를 사용하기 때문에 (별도 디렉터리에 생성된) dll 파일을 찾을 수 없어 오류가 발생합니다.
이 문제를 해결할 수 있는 한 가지 방법으로, Condition 속성을 고려할 수 있는데요,
MSBuild conditions
; https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions?view=vs-2022
그렇다면 이제 필요한 것은 Condition 식 내에 Web Application 프로젝트를 구분할 수 있는 적절한 속성을 찾아야 합니다. 음... 어떤 것이 좋을까요? ^^; 이를 위해 Web Application 프로젝트용의 csproj를 살펴보면,
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{9F65F804-0A96-4FC4-9F32-6A8DC1A897D7}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WebApplication1</RootNamespace>
<AssemblyName>WebApplication1</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<Use64BitIISExpress />
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
ProjectTypeGuids, UseIISExpress 정도가 후보로 떠오릅니다. (아무거나 고른) ProjectTypeGuids의 경우
"349c5851-65df-11da-9384-00065b846f21" 값이 있다면 MVC 프로젝트 유형을 나타내므로 Directory.Build.props에 아래와 같이 올바른 문법으로 사용해 볼 수 있지만,
<Project>
<PropertyGroup Condition="$(ProjectTypeGuids.IndexOf('349c5851-65df-11da-9384-00065b846f21')) == -1">
<OutputPath>$(BaseOutputPath)\$(Platform)\$(Configuration)\</OutputPath>
<BaseIntermediateOutputPath>$(BaseOutputPath)\temp</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)\$(Platform)\$(Configuration)\</IntermediateOutputPath>
...[생략]...
</PropertyGroup>
</Project>
실제로 저렇게 해보면 의도한 대로 동작하지 않습니다. 그 이유는, Directory.Build.props의 평가가 너무 이른 시기에 완료되므로 ProjectTypeGuids의 값이 저 시점에는 비어 있기 때문입니다. (UseIISExpress를 사용해도 마찬가지입니다.)
아이러니하게도, Directory.Build.props 파일의 속성이 프로젝트에 대해 매우 이른 시기에 적용된다는 점은 장점이면서도 단점이 된 것입니다. 즉, OutputPath 등의 적용이 프로젝트 로딩 초기에 이뤄지므로 /bin, 또는 /obj 디렉터리가 생성되지 않을 수 있었던 건데요, 오히려 그런 이른 평가 시점으로 인해 Condition 조건에 여타 다른 속성을 사용할 수 없다는 제약으로 작용합니다.
아쉬운 대로, 이런 제약을 해결할 수 있는 방법이 Target을 경유해 속성 변경을 하는 것입니다. 가령,
PrepareForBuild 이전에 실행하도록 Target을 정의하고, 그 안에서 CallTarget + Condition을 적용하는 식으로 우회할 수 있습니다.
<Target Name="RollbackProperties">
<PropertyGroup>
<BaseOutputPath>$(ProjectDir)bin\</BaseOutputPath>
<OutputPath>$(ProjectDir)bin\</OutputPath>
<BaseIntermediateOutputPath>$(ProjectDir)obj\$(Configuration)\</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(ProjectDir)obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
</Target>
<Target Name="CSharpUseTempDirectory" BeforeTargets="PrepareForBuild">
<CallTarget Condition="'$(UseIISExpress)' == 'true'" Targets="RollbackProperties" />
</Target>
(물론, 시점의 변화로 인해 위와 같은 상황에서 의도했던
/obj, /bin 디렉터리 생성을 막을 수는 없습니다.)
참고로, Condition 등의 조건식에 문자열을 중복 사용할 때는 escape 처리를 해야 합니다. 가령, 다음과 같이 사용하게 되면,
Condition="'$(ProjectTypeGuids.IndexOf('349c5851-65df-11da-9384-00065b846f21'))' == '-1'"
2중으로 홑따옴표를 사용해 오류가 발생합니다.
error : An unexpected token "..." was found at character position ... in condition
이런 경우 내부에 중첩된 홑따옴표를 backtick(`)으로 escape 처리하면 됩니다.
Condition="'$(ProjectTypeGuids.IndexOf(`349c5851-65df-11da-9384-00065b846f21`))' == '-1'"
만약 특정 속성의 값이 궁금하다면
Message Task를 사용해 확인해 볼 수 있습니다. 가령 다음과 같이 속성을 정의했다면,
<Project>
...[생략]...
<PropertyGroup>
<TestProp>$(ProjectTypeGuids)</TestProp>
</PropertyGroup>
</Project>
적절하게 Target을 정의한 후, Message Task를 추가하면 빌드 시에 Output 창을 통해 속성 값을 확인할 수 있습니다.
<Project>
...[생략]...
<Target Name="mywork" AfterTargets="CoreBuild">
<Message Importance="high" Text="[TEST] $(TestProp)"/>
</Target>
</Project>
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]