عنوان:

‫رفع دوگانگی محیط اجرای MSBuild در NET 10 RC 2.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۷/۲۵ ۱۲:۴۴
آدرس: www.dntips.ir
برای سال‌ها، توسعه‌دهندگان دات‌نت که تسک‌های سفارشی MSBuild می‌نوشتند، با یک چالش اساسی دست‌وپنجه نرم می‌کردند: دوگانگی محیط اجرای MSBuild. اکنون، .NET 10 RC 2 در حال ارائه راه‌حلی است که این مشکل تاریخی را برطرف کرده و توسعه تسک‌ها را به شکل چشمگیری ساده‌تر می‌کند.

چالش تاریخی: دوگانگی MSBuild

MSBuild سیستم ساخت (build system) بنیادین برای دات‌نت است. این ابزار نه‌تنها پروژه‌های شما را کامپایل و پکیج می‌کند (مانند دستورات dotnet build و dotnet pack)، بلکه به‌عنوان یک ارائه‌دهنده اطلاعات در مورد پروژه‌ها نیز عمل می‌کند (مثلاً در dotnet list package).
مشکل از اینجا ناشی می‌شد که ما با دو نسخه از MSBuild سروکار داشتیم:
  • MSBuild در dotnet CLI: زمانی که شما دستوری مانند dotnet build را اجرا می‌کنید، از نسخه‌ی MSBuild استفاده می‌شود که همراه با .NET SDK مدرن (مثلاً .NET 8, 9, 10) ارائه شده و بر روی پلتفرم .NET اجرا می‌شود.
  • MSBuild در Visual Studio: زمانی که شما از داخل ویژوال استودیو پروژه خود را بیلد می‌کنید یا مستقیماً msbuild.exe را فراخوانی می‌کنید، از نسخه‌ای از MSBuild استفاده می‌شود که همراه با ویژوال استودیو نصب شده و یک اپلیکیشن .NET Framework است.
این دوگانگی به این معنا بود که اگر شما یک تسک MSBuild سفارشی می‌نوشتید و آن را برای .NET مدرن (مثلاً net8.0) هدف‌گذاری می‌کردید تا از آخرین APIهای دات‌نت بهره‌مند شوید، آن تسک در dotnet CLI به خوبی کار می‌کرد، اما در Visual Studio یا هنگام فراخوانی مستقیم msbuild.exe با شکست مواجه می‌شد.
راه‌حل تا به امروز، چند-هدف‌گذاری (multi-targeting) تسک‌ها بود. نویسندگان تسک مجبور بودند کتابخانه تسک خود را هم برای .NET (جهت dotnet CLI) و هم برای .NET Framework (اغلب netstandard2.0 یا net472 جهت Visual Studio) کامپایل کنند. این فرآیند نه‌تنها پیچیده بود، بلکه توسعه‌دهندگان را از استفاده از ویژگی‌های مدرن دات‌نت در تسک‌هایشان محروم می‌کرد.

راه‌حل در .NET 10: پل ارتباطی بین دو جهان

خبر بزرگ این است: با شروع .NET 10 (و Visual Studio 2026)، فایل اجرایی msbuild.exe و ویژوال استودیو قادر خواهند بود تسک‌های MSBuild را که برای .NET مدرن ساخته شده‌اند، اجرا کنند!
این تغییر به این معنی است که شما اکنون می‌توانید تسک‌های خود را فقط برای .NET هدف‌گذاری کنید و مطمئن باشید که در همه‌جا—چه در dotnet CLI و چه در داخل Visual Studio—به درستی کار خواهند کرد. این امر مشکلات مربوط به چند-هدف‌گذاری و مدیریت وابستگی‌های .NET Framework را که به‌طور ضمنی در MSBuild قدیمی وجود داشت، از بین می‌برد.

پیاده‌سازی: چگونه تسک‌های خود را سازگار کنیم؟

برای نویسندگان تسک، فعال‌سازی این رفتار جدید بسیار ساده است. کافی است تعریف UsingTask خود را در فایل .targets تغییر دهید تا MSBuild از نحوه اجرای تسک شما مطلع شود.

روش ساده

کافی است دو ویژگی جدید Runtime و TaskFactory را اضافه کنید:
<UsingTask TaskName="MyTask"
    AssemblyFile="path\to\MyTask.dll"
    Runtime="NET"                 # این ویژگی جدید است!
    TaskFactory="TaskHostFactory" # و این هم جدید است!
/>
این دو ویژگی به موتور MSBuild می‌گویند:
  • Runtime="NET": به MSBuild می‌گوید که این تسک برای .NET مدرن ساخته شده است (در مقابل .NET Framework).
  • TaskFactory="TaskHostFactory": به MSBuild دستور می‌دهد که از TaskHostFactory برای اجرای تسک استفاده کند. TaskHostFactory یک قابلیت موجود در MSBuild است که اجازه می‌دهد تسک‌ها خارج از فرآیند اصلی (out-of-process) اجرا شوند.
در اصل، وقتی MSBuild (که روی .NET Framework در ویژوال استودیو در حال اجراست) به این تسک می‌رسد، یک فرآیند کارگر (worker process) جدید مبتنی بر .NET مدرن راه‌اندازی می‌کند تا تسک شما را اجرا کند و سپس از طریق ارتباطات بین-فرایندی (IPC) با آن ارتباط برقرار می‌کند.

ملاحظات کارایی و بهینه‌سازی

مثال بالا ساده‌ترین راه است، اما یک محدودیت دارد: استفاده از TaskHostFactoryهمیشه تسک را به‌صورت out-of-process اجرا می‌کند، حتی زمانی که در dotnet CLI هستید (جایی که نیازی به این کار نیست).
این اجرای OOP مقداری سربار (overhead) جزئی به دلیل ارتباطات IPC دارد. برای اکثر تسک‌ها، این سربار ناچیز است. اما برای تسک‌هایی که بارها در طول یک بیلد اجرا می‌شوند یا لاگ‌نویسی سنگینی دارند، این سربار ممکن است قابل توجه باشد.

روش بهینه‌سازی شده

با کمی کار بیشتر، می‌توانیم تسک را طوری پیکربندی کنیم که فقط در مواقع لزوم (یعنی در ویژوال استودیو) به‌صورت out-of-process اجرا شود و در dotnet CLI به‌صورت بهینه (in-process) اجرا گردد:
<UsingTask TaskName="MyTask"
    AssemblyFile="path\to\MyTask.dll"
    Runtime="NET"
    TaskFactory="TaskHostFactory"
        Condition="$(MSBuildRuntimeType) == 'Full'" 
/>
<UsingTask TaskName="MyTask"
    AssemblyFile="path\to\MyTask.dll"
    Runtime="NET"
        Condition="$(MSBuildRuntimeType) == 'Core'" 
/>
در اینجا ما از ویژگی Condition در MSBuild استفاده کرده‌ایم:
  • $(MSBuildRuntimeType) == 'Full': این شرط زمانی درست است که MSBuild در محیط .NET Framework (مانند ویژوال استودیو یا msbuild.exe) اجرا می‌شود. در این حالت، ما از TaskHostFactory برای اجرای OOP استفاده می‌کنیم.
  • $(MSBuildRuntimeType) == 'Core': این شرط زمانی درست است که MSBuild در محیط .NET مدرن (مانند dotnet CLI) اجرا می‌شود. در این حالت، ما TaskFactory را مشخص نمی‌کنیم و تسک به‌طور عادی و in-process (درون فرآیند اصلی) بارگذاری می‌شود که بهترین کارایی را دارد.

محدودیت‌ها و آینده پیش رو

در حال حاضر محدودیت‌های فنی کوچکی نیز وجود دارد. قابل توجه‌ترین آن‌ها این است که ویژگی Host Object در تسک‌های MSBuild هنوز برای تسک‌های .NET که به‌صورت out-of-process اجرا می‌شوند، پشتیبانی نمی‌شود. Host Objects مکانیزمی پیشرفته است که به تسک اجازه می‌دهد مستقیماً با محیط میزبان (مانند IDE ویژوال استودیو) تعامل داشته باشد. اگر تسک شما به Host Object متکی است، فعلاً در ویژوال استودیو کار نخواهد کرد.
تیم دات‌نت اعلام کرده است که این اولین قدم در سفری طولانی‌تر برای انعطاف‌پذیرتر کردن MSBuild است. برنامه‌های آینده عبارتند از:
  • کشف و بارگذاری خودکار تسک‌های .NET بدون نیاز به تعیین دستی Runtime یا TaskFactory.
  • کاهش سربار عملکردی IPC بین موتور MSBuild و تسک‌های out-of-process.
  • پشتیبانی از Host Object برای تسک‌های .NET که به‌صورت out-of-process اجرا می‌شوند.


مشاهده مطلب اصلی