سیستم های ساخت مبتنی بر وظیفه

این صفحه سیستم‌های ساخت مبتنی بر وظیفه، نحوه کار آنها و برخی از عوارضی که ممکن است با سیستم‌های مبتنی بر وظیفه رخ دهد را پوشش می‌دهد. پس از اسکریپت های پوسته، سیستم های ساخت مبتنی بر وظیفه، تکامل منطقی بعدی ساختمان هستند.

درک سیستم های ساخت مبتنی بر وظیفه

در یک سیستم ساخت مبتنی بر وظیفه، واحد اساسی کار وظیفه است. هر وظیفه یک اسکریپت است که می تواند هر نوع منطقی را اجرا کند و وظایف، وظایف دیگری را به عنوان وابستگی مشخص می کنند که باید قبل از آنها اجرا شوند. اکثر سیستم های ساخت اصلی که امروزه مورد استفاده قرار می گیرند، مانند Ant، ​​Maven، Gradle، Grunt و Rake، مبتنی بر وظیفه هستند. به جای اسکریپت های پوسته، اکثر سیستم های ساخت مدرن به مهندسان نیاز دارند که فایل های ساختی ایجاد کنند که نحوه اجرای ساخت را توضیح دهد.

این مثال را از کتابچه راهنمای مورچه بگیرید:

<project name="MyProject" default="dist" basedir=".">
   <description>
     simple example build file
   </description>
   <!-- set global properties for this build -->
   <property name="src" location="src"/>
   <property name="build" location="build"/>
   <property name="dist" location="dist"/>

   <target name="init">
     <!-- Create the time stamp -->
     <tstamp/>
     <!-- Create the build directory structure used by compile -->
     <mkdir dir="${build}"/>
   </target>
   <target name="compile" depends="init"
       description="compile the source">
     <!-- Compile the Java code from ${src} into ${build} -->
     <javac srcdir="${src}" destdir="${build}"/>
   </target>
   <target name="dist" depends="compile"
       description="generate the distribution">
     <!-- Create the distribution directory -->
     <mkdir dir="${dist}/lib"/>
     <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
     <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
   </target>
   <target name="clean"
       description="clean up">
     <!-- Delete the ${build} and ${dist} directory trees -->
     <delete dir="${build}"/>
     <delete dir="${dist}"/>
   </target>
</project>

بیلدفایل به زبان XML نوشته شده است و برخی فراداده های ساده را در مورد ساخت به همراه فهرستی از وظایف (تگ های <target> در XML) تعریف می کند. (Ant از کلمه target برای نشان دادن یک کار استفاده می کند و از کلمه task برای اشاره به دستورات استفاده می کند.) هر وظیفه لیستی از دستورات ممکن تعریف شده توسط Ant را اجرا می کند که در اینجا شامل ایجاد و حذف دایرکتوری ها، اجرای javac و ایجاد یک JAR می شود. فایل. این مجموعه از دستورات را می توان توسط افزونه های ارائه شده توسط کاربر گسترش داد تا هر نوع منطقی را پوشش دهد. هر وظیفه همچنین می‌تواند وظایفی را که به آن‌ها وابسته است را از طریق ویژگیdependent تعریف کند. همانطور که در شکل 1 مشاهده می شود، این وابستگی ها یک نمودار غیر چرخه ای را تشکیل می دهند.

نمودار اکریلیک که وابستگی ها را نشان می دهد

شکل 1. یک نمودار غیر چرخه ای که وابستگی ها را نشان می دهد

کاربران با ارائه وظایف به ابزار خط فرمان Ant، ​​ساخت‌ها را انجام می‌دهند. به عنوان مثال، هنگامی که کاربر ant dist را تایپ می کند، Ant مراحل زیر را انجام می دهد:

  1. فایلی به نام build.xml را در دایرکتوری فعلی بارگذاری می کند و آن را برای ایجاد ساختار نمودار نشان داده شده در شکل 1 تجزیه می کند.
  2. به دنبال وظیفه ای به نام dist می گردد که در خط فرمان ارائه شده است و متوجه می شود که به وظیفه ای به نام compile وابستگی دارد.
  3. به دنبال وظیفه ای به نام compile می گردد و متوجه می شود که به وظیفه ای که init نام دارد وابستگی دارد.
  4. به دنبال کار با نام init می گردد و متوجه می شود که هیچ وابستگی ندارد.
  5. دستورات تعریف شده در task init را اجرا می کند.
  6. دستورات تعریف شده در کار compile را با توجه به اینکه تمام وابستگی های آن کار اجرا شده است، اجرا می کند.
  7. با توجه به اینکه تمام وابستگی های آن کار اجرا شده است، دستورات تعریف شده در وظیفه dist را اجرا می کند.

در پایان، کد اجرا شده توسط Ant هنگام اجرای وظیفه dist معادل اسکریپت پوسته زیر است:

./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*

هنگامی که نحو حذف می شود، فایل ساخت و اسکریپت ساخت در واقع خیلی متفاوت نیستند. اما ما قبلاً با انجام این کار چیزهای زیادی به دست آورده ایم. می‌توانیم فایل‌های بیلد جدید را در فهرست‌های دیگر ایجاد کنیم و آن‌ها را به هم پیوند دهیم. ما به راحتی می توانیم وظایف جدیدی را اضافه کنیم که به کارهای موجود به روش های دلخواه و پیچیده بستگی دارد. ما فقط باید نام یک وظیفه را به ابزار خط فرمان ant منتقل کنیم و همه چیزهایی که باید اجرا شوند را تعیین می کند.

Ant یک نرم‌افزار قدیمی است که در ابتدا در سال 2000 منتشر شد. ابزارهای دیگری مانند Maven و Gradle در سال‌های بعد روی Ant بهبود یافتند و اساساً با افزودن ویژگی‌هایی مانند مدیریت خودکار وابستگی‌های خارجی و نحو پاک‌تر بدون XML جایگزین آن شدند. اما ماهیت این سیستم‌های جدید یکسان باقی می‌ماند: آنها به مهندسان اجازه می‌دهند تا اسکریپت‌های ساخت را به صورت اصولی و مدولار به عنوان وظایف بنویسند و ابزارهایی برای اجرای آن وظایف و مدیریت وابستگی‌ها در میان آنها فراهم کنند.

سمت تاریک سیستم های ساخت مبتنی بر وظیفه

از آنجایی که این ابزارها اساساً به مهندسان اجازه می دهند هر اسکریپت را به عنوان یک کار تعریف کنند، بسیار قدرتمند هستند و به شما امکان می دهند تقریباً هر کاری را که تصور کنید با آنها انجام دهید. اما این قدرت با اشکالاتی همراه است و با پیچیده‌تر شدن اسکریپت‌های ساخت آنها، کار با سیستم‌های ساخت مبتنی بر وظیفه دشوار می‌شود. مشکل چنین سیستم‌هایی این است که در نهایت قدرت زیادی به مهندسان می‌دهند و قدرت کافی به سیستم نمی‌دهند . از آنجا که سیستم هیچ ایده ای ندارد که اسکریپت ها چه کار می کنند، عملکرد آسیب می بیند، زیرا باید در نحوه برنامه ریزی و اجرای مراحل ساخت بسیار محافظه کار باشد. و هیچ راهی برای سیستم وجود ندارد که تأیید کند که هر اسکریپت همان کاری را که باید انجام می دهد، انجام می دهد، بنابراین اسکریپت ها تمایل به افزایش پیچیدگی دارند و در نهایت به چیز دیگری تبدیل می شوند که نیاز به اشکال زدایی دارد.

مشکل موازی کردن مراحل ساخت

ایستگاه های کاری توسعه مدرن بسیار قدرتمند هستند، با چندین هسته که قادر به اجرای چندین مرحله ساخت به صورت موازی هستند. اما سیستم‌های مبتنی بر وظیفه اغلب قادر به موازی کردن اجرای کار نیستند، حتی زمانی که به نظر می‌رسد باید بتوانند. فرض کنید که وظیفه A به وظایف B و C بستگی دارد. چون وظایف B و C هیچ وابستگی به یکدیگر ندارند، آیا اجرای همزمان آنها امن است تا سیستم بتواند سریعتر به وظیفه A برسد؟ شاید، اگر آنها به هیچ یک از منابع مشابه دست نزنند. اما شاید نه - شاید هر دو از یک فایل برای ردیابی وضعیت خود استفاده می کنند و اجرای همزمان آنها باعث تضاد می شود. به طور کلی هیچ راهی برای سیستم وجود ندارد که بداند، بنابراین یا باید این تضادها را به خطر بیاندازد (که منجر به مشکلات ساخت نادر اما بسیار دشوار می شود)، یا باید کل ساخت را محدود به اجرا در یک رشته در یک رشته کند. فرآیند واحد این می تواند اتلاف بزرگی برای یک ماشین توسعه دهنده قدرتمند باشد و امکان توزیع بیلد در چندین ماشین را کاملاً رد می کند.

مشکل در اجرای ساخت های افزایشی

یک سیستم ساخت خوب به مهندسان این امکان را می دهد که ساخت های افزایشی قابل اعتمادی را انجام دهند به طوری که یک تغییر کوچک نیازی به بازسازی کل پایه کد از ابتدا نداشته باشد. این امر به ویژه در صورتی مهم است که سیستم ساخت به دلایل ذکر شده کند باشد و نتواند مراحل ساخت را موازی کند. اما متأسفانه، سیستم های ساخت مبتنی بر وظیفه در اینجا نیز مشکل دارند. از آنجا که وظایف می توانند هر کاری را انجام دهند، به طور کلی هیچ راهی برای بررسی اینکه آیا قبلا انجام شده اند یا خیر وجود ندارد. بسیاری از وظایف به سادگی مجموعه ای از فایل های منبع را می گیرند و یک کامپایلر را برای ایجاد مجموعه ای از باینری ها اجرا می کنند. بنابراین، اگر فایل‌های منبع اصلی تغییر نکرده باشند، نیازی به اجرا مجدد ندارند. اما بدون اطلاعات اضافی، سیستم نمی‌تواند این را به طور قطعی بگوید - شاید کار فایلی را دانلود می‌کند که می‌توانست تغییر کرده باشد، یا ممکن است مهر زمانی بنویسد که در هر اجرا متفاوت باشد. برای تضمین صحت، سیستم معمولاً باید هر کار را در طول هر ساخت مجدداً اجرا کند. برخی از سیستم‌های ساخت سعی می‌کنند ساخت‌های افزایشی را با اجازه دادن به مهندسان برای تعیین شرایطی که در آن یک کار باید دوباره اجرا شود، فعال کنند. گاهی اوقات این امکان پذیر است، اما اغلب مشکلی بسیار پیچیده تر از آن چیزی است که به نظر می رسد. برای مثال، در زبان‌هایی مانند C++ که به فایل‌ها اجازه می‌دهد مستقیماً توسط فایل‌های دیگر اضافه شوند، تعیین کل مجموعه فایل‌هایی که باید برای تغییرات مشاهده شوند بدون تجزیه منابع ورودی غیرممکن است. مهندسان اغلب از میانبرها استفاده می کنند و این میانبرها می تواند منجر به مشکلات نادر و خسته کننده شود که در آن نتیجه کار حتی زمانی که نباید استفاده می شود، دوباره استفاده می شود. وقتی این اتفاق مکرر می‌افتد، مهندسان عادت می‌کنند قبل از هر ساخت و ساز تمیز بدوند تا به حالتی تازه برسند، و در وهله اول هدف از ساختن افزایشی را کاملاً از بین می‌برند. فهمیدن اینکه چه زمانی یک کار باید دوباره اجرا شود به طرز شگفت انگیزی ظریف است و کاری است که ماشین ها بهتر از انسان ها آن را انجام می دهند.

مشکل در نگهداری و رفع اشکال اسکریپت ها

در نهایت، اسکریپت های ساخت تحمیل شده توسط سیستم های ساخت مبتنی بر وظیفه اغلب کار با آنها دشوار است. اگرچه اسکریپت‌های ساخت اغلب کمتر مورد بررسی قرار می‌گیرند، درست مانند سیستم در حال ساخت کد هستند و مکان‌هایی آسان برای پنهان کردن باگ‌ها هستند. در اینجا چند نمونه از باگ‌هایی وجود دارد که هنگام کار با یک سیستم ساخت مبتنی بر وظیفه بسیار رایج هستند:

  • وظیفه A به وظیفه B برای تولید یک فایل خاص به عنوان خروجی بستگی دارد. صاحب وظیفه B متوجه نمی شود که وظایف دیگر به آن متکی هستند، بنابراین آن را تغییر می دهند تا خروجی را در مکان دیگری تولید کنند. تا زمانی که فردی سعی کند وظیفه A را اجرا کند و متوجه شود که با شکست مواجه شده است، نمی توان آن را شناسایی کرد.
  • وظیفه A به وظیفه B بستگی دارد، که به وظیفه C بستگی دارد، که یک فایل خاص را به عنوان خروجی مورد نیاز کار A تولید می کند. صاحب وظیفه B تصمیم می گیرد که دیگر نیازی به تکلیف C نداشته باشد، که باعث ایجاد وظیفه می شود. A شکست می خورد حتی اگر وظیفه B اصلاً به وظیفه C اهمیتی نمی دهد!
  • توسعه‌دهنده یک کار جدید به‌طور تصادفی در مورد ماشینی که وظیفه را اجرا می‌کند، فرض می‌کند، مانند مکان یک ابزار یا مقدار متغیرهای محیطی خاص. این کار روی دستگاه آنها کار می کند، اما هر زمان که توسعه دهنده دیگری آن را امتحان کند، با شکست مواجه می شود.
  • یک کار حاوی یک جزء غیر قطعی است، مانند دانلود یک فایل از اینترنت یا افزودن یک مهر زمانی به یک ساخت. اکنون، افراد هر بار که ساخت را اجرا می‌کنند، نتایج بالقوه متفاوتی دریافت می‌کنند، به این معنی که مهندسان همیشه نمی‌توانند شکست‌ها یا خرابی‌های یکدیگر را که در یک سیستم ساخت خودکار رخ می‌دهد، بازتولید کرده و برطرف کنند.
  • وظایف با وابستگی های متعدد می توانند شرایط مسابقه را ایجاد کنند. اگر وظیفه A به هر دو کار B و C بستگی دارد، و کار B و C هر دو یک فایل را تغییر می دهند، وظیفه A بسته به اینکه کدام یک از وظایف B و C اول تمام شود، نتیجه متفاوتی دریافت می کند.

هیچ راه همه‌منظوره‌ای برای حل این مشکلات عملکرد، صحت یا نگهداری در چارچوب وظیفه‌محور ارائه‌شده در اینجا وجود ندارد. تا زمانی که مهندسان بتوانند کد دلخواه بنویسند که در حین ساخت اجرا می شود، سیستم نمی تواند اطلاعات کافی برای اجرای سریع و صحیح بیلدها را داشته باشد. برای حل مشکل، باید مقداری نیرو را از دست مهندسان خارج کنیم و آن را دوباره به دست سیستم بسپاریم و نقش سیستم را نه به عنوان وظایف در حال اجرا، بلکه به عنوان تولید مصنوعات دوباره مفهوم سازی کنیم.

این رویکرد منجر به ایجاد سیستم‌های ساخت مبتنی بر مصنوعات مانند Blaze و Bazel شد.