سیستم های ساخت مبتنی بر مصنوعات

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

سیستم های ساخت مبتنی بر مصنوع دارای تعداد کمی از وظایف تعریف شده توسط سیستم هستند که مهندسان می توانند به صورت محدود پیکربندی کنند. مهندسان هنوز به سیستم می گویند که چه چیزی بسازد، اما سیستم ساخت تعیین می کند که چگونه آن را بسازد. همانند سیستم‌های ساخت مبتنی بر وظیفه، سیستم‌های ساخت مبتنی بر مصنوعات، مانند Bazel، هنوز دارای فایل‌های بیلد هستند، اما محتویات آن بیلدفایل‌ها بسیار متفاوت است. فایل های بیلد در Bazel به جای اینکه مجموعه ای ضروری از دستورات در یک زبان برنامه نویسی کامل تورینگ باشد که نحوه تولید خروجی را توضیح می دهد، یک مانیفست اعلامی است که مجموعه ای از مصنوعات را برای ساخت، وابستگی های آنها و مجموعه محدودی از گزینه ها را توصیف می کند که بر نحوه آنها تأثیر می گذارد. 'بازسازی. هنگامی که مهندسان bazel را در خط فرمان اجرا می کنند، مجموعه ای از اهداف را برای ساختن مشخص می کنند ( چی ) و Bazel مسئول پیکربندی، اجرا و زمان بندی مراحل کامپایل ( چگونه ) است. از آنجایی که سیستم ساخت اکنون کنترل کاملی بر روی ابزارهایی دارد که چه زمانی اجرا شوند، می‌تواند تضمین‌های بسیار قوی‌تری ایجاد کند که به آن اجازه می‌دهد بسیار کارآمدتر باشد و در عین حال صحت را تضمین کند.

یک دیدگاه عملکردی

ایجاد قیاس بین سیستم های ساخت مبتنی بر مصنوع و برنامه نویسی عملکردی آسان است. زبان‌های برنامه‌نویسی ضروری سنتی (مانند جاوا، سی و پایتون) فهرست‌هایی از دستورات را مشخص می‌کنند که باید یکی پس از دیگری اجرا شوند، به همان روشی که سیستم‌های ساخت مبتنی بر وظیفه به برنامه‌نویسان اجازه می‌دهند یک سری مراحل را برای اجرا تعریف کنند. زبان های برنامه نویسی تابعی (مانند Haskell و ML)، در مقابل، بیشتر شبیه به یک سری معادلات ریاضی ساختار یافته اند. در زبان های تابعی، برنامه نویس یک محاسبات را برای انجام توصیف می کند، اما جزئیات زمان و نحوه دقیق اجرای آن محاسبات را به کامپایلر واگذار می کند.

این به ایده اعلام یک مانیفست در یک سیستم ساخت مبتنی بر مصنوعات و اجازه دادن به سیستم برای کشف نحوه اجرای ساخت اشاره دارد. بسیاری از مشکلات را نمی توان به راحتی با استفاده از برنامه نویسی تابعی بیان کرد، اما مواردی که از آن سود زیادی می برند: این زبان اغلب می تواند به طور پیش پا افتاده چنین برنامه هایی را موازی کند و تضمین های قوی در مورد صحت آنها ارائه دهد که در یک زبان ضروری غیرممکن است. ساده ترین مشکلات برای بیان با استفاده از برنامه نویسی تابعی، مواردی است که به سادگی شامل تبدیل یک قطعه داده به دیگری با استفاده از یک سری قوانین یا توابع است. و این دقیقاً همان چیزی است که یک سیستم ساختنی است: کل سیستم عملاً یک تابع ریاضی است که فایل های منبع (و ابزارهایی مانند کامپایلر) را به عنوان ورودی می گیرد و باینری ها را به عنوان خروجی تولید می کند. بنابراین، تعجب آور نیست که به خوبی کار می کند تا یک سیستم ساخت را بر اساس اصول برنامه نویسی تابعی قرار دهد.

درک سیستم های ساخت مبتنی بر مصنوعات

سیستم ساخت گوگل، Blaze، اولین سیستم ساخت مبتنی بر مصنوعات بود. Bazel نسخه منبع باز Blaze است.

در اینجا یک buildfile (که معمولاً BUILD نام دارد) در Bazel به نظر می رسد:

java_binary(
    name = "MyBinary",
    srcs = ["MyBinary.java"],
    deps = [
        ":mylib",
    ],
)
java_library(
    name = "mylib",
    srcs = ["MyLibrary.java", "MyHelper.java"],
    visibility = ["//java/com/example/myproduct:__subpackages__"],
    deps = [
        "//java/com/example/common",
        "//java/com/example/myproduct/otherlib",
    ],
)

در Bazel، فایل‌های BUILD اهداف را تعریف می‌کنند—دو نوع هدف در اینجا java_binary و java_library هستند. هر هدف مربوط به یک مصنوع است که می تواند توسط سیستم ایجاد شود: اهداف باینری فایل های باینری تولید می کنند که می توانند مستقیماً اجرا شوند و اهداف کتابخانه ای کتابخانه هایی تولید می کنند که می توانند توسط باینری ها یا کتابخانه های دیگر استفاده شوند. هر هدف دارای:

  • name : نحوه ارجاع هدف در خط فرمان و سایر اهداف
  • srcs : فایل های منبعی که باید برای ایجاد آرتیفکت برای هدف کامپایل شوند
  • deps : اهداف دیگری که باید قبل از این هدف ساخته شوند و به آن پیوند داده شوند

وابستگی ها می توانند در یک بسته باشند (مانند وابستگی MyBinary به :mylib ) یا در یک بسته متفاوت در سلسله مراتب منبع یکسان (مانند وابستگی mylib به //java/com/example/common ).

همانند سیستم‌های ساخت مبتنی بر وظیفه، ساخت‌ها را با استفاده از ابزار خط فرمان Bazel انجام می‌دهید. برای ساخت هدف MyBinary ، شما باید bazel build :MyBinary را اجرا کنید. پس از اینکه برای اولین بار این دستور را در یک مخزن تمیز وارد کردید، Bazel:

  1. هر فایل BUILD را در فضای کاری تجزیه می کند تا نموداری از وابستگی ها بین مصنوعات ایجاد کند.
  2. از نمودار برای تعیین وابستگی های انتقالی MyBinary می کند. یعنی هر هدفی که MyBinary به آن وابسته است و هر هدفی که آن اهداف به آن وابسته هستند، به صورت بازگشتی.
  3. هر یک از آن وابستگی ها را به ترتیب ایجاد می کند. Bazel با ساختن هر هدفی که وابستگی دیگری ندارد شروع می کند و پیگیری می کند که کدام وابستگی هنوز برای هر هدف باید ساخته شود. به محض اینکه تمام وابستگی های یک هدف ساخته شد، Bazel شروع به ساخت آن هدف می کند. این فرآیند تا زمانی ادامه می‌یابد که هر یک از وابستگی‌های انتقالی MyBinary ساخته شوند.
  4. MyBinary را می‌سازد تا یک باینری اجرایی نهایی تولید کند که به تمام وابستگی‌هایی که در مرحله 3 ساخته شده‌اند پیوند می‌دهد.

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

با این حال، مزایا فراتر از موازی سازی است. نکته بعدی که این رویکرد به ما می‌دهد زمانی آشکار می‌شود که توسعه‌دهنده برای بار دوم بدون ایجاد تغییر، bazel build :MyBinary را تایپ می‌کند: Bazel در کمتر از یک ثانیه با پیامی مبنی بر به‌روز بودن هدف خارج می‌شود. این به دلیل الگوی برنامه نویسی کاربردی که قبلاً در مورد آن صحبت کردیم امکان پذیر است - بازل می داند که هر هدف تنها نتیجه اجرای یک کامپایلر جاوا است و می داند که خروجی کامپایلر جاوا فقط به ورودی های آن بستگی دارد، بنابراین تا زمانی که ورودی ها تغییر نکرده اند، خروجی را می توان مجددا استفاده کرد. و این تحلیل در هر سطحی کار می کند. اگر MyBinary.java تغییر کند، Bazel می داند که MyBinary mylib استفاده کند. اگر یک فایل منبع برای //java/com/example/common تغییر کند، Bazel می داند که آن کتابخانه، mylib و MyBinary را بازسازی کند، اما از //java/com/example/myproduct/otherlib استفاده کند. از آنجایی که Bazel در مورد ویژگی‌های ابزاری که در هر مرحله اجرا می‌کند می‌داند، می‌تواند هر بار تنها مجموعه حداقلی از مصنوعات را بازسازی کند و در عین حال تضمین کند که ساخت‌های قدیمی تولید نمی‌کند.

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

دیگر ترفندهای بازل

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

ابزارها به عنوان وابستگی

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

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

Bazel بخش دوم مشکل، استقلال پلت فرم را با تنظیم تنظیمات ساخت حل می کند. به جای اینکه اهداف مستقیماً به ابزارهایشان وابسته باشند، به انواع پیکربندی ها بستگی دارند:

  • پیکربندی میزبان : ابزارهای ساختمانی که در حین ساخت اجرا می شوند
  • پیکربندی هدف : ساخت باینری که در نهایت درخواست کردید

گسترش سیستم ساخت

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

برای تعریف یک قانون در Bazel، نویسنده قانون ورودی‌های مورد نیاز قانون (به شکل ویژگی‌های ارسال شده در فایل BUILD ) و مجموعه ثابتی از خروجی‌هایی را که قانون تولید می‌کند، اعلام می‌کند. نویسنده همچنین اقداماتی را که توسط آن قانون ایجاد می شود، تعریف می کند. هر اکشن ورودی ها و خروجی های خود را اعلام می کند، یک فایل اجرایی خاص را اجرا می کند یا رشته خاصی را در یک فایل می نویسد، و می تواند از طریق ورودی و خروجی خود به اقدامات دیگر متصل شود. این بدان معناست که اکشن‌ها پایین‌ترین واحد ترکیب‌پذیر در سیستم ساخت هستند - یک اکشن می‌تواند هر کاری را که می‌خواهد انجام دهد تا زمانی که فقط از ورودی‌ها و خروجی‌های اعلام‌شده‌اش استفاده کند، و Bazel از زمان‌بندی اقدامات و ذخیره‌سازی نتایج آن‌ها در زمان مناسب مراقبت می‌کند.

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

منزوی کردن محیط

عملکردها به نظر می رسد که ممکن است با مشکلات مشابه وظایف در سیستم های دیگر مواجه شوند - آیا هنوز نمی توان اقداماتی را نوشت که هر دو در یک فایل می نویسند و در نهایت با یکدیگر تضاد دارند؟ در واقع، Bazel این درگیری ها را با استفاده از sandboxing غیرممکن می کند. در سیستم‌های پشتیبانی‌شده، هر اقدامی از طریق یک جعبه ایمنی سیستم فایل از هر عمل دیگری جدا می‌شود. عملاً، هر اقدامی می‌تواند فقط یک نمای محدود از سیستم فایل را ببیند که شامل ورودی‌هایی است که اعلام کرده و خروجی‌هایی که تولید کرده است. این توسط سیستم هایی مانند LXC در لینوکس، همان فناوری پشت Docker اعمال می شود. این بدان معناست که تداخل عملکردها با یکدیگر غیرممکن است، زیرا قادر به خواندن فایل‌هایی که اعلام نمی‌کنند نیستند، و هر فایلی که می‌نویسند اما اعلام نمی‌کنند، پس از پایان عمل دور ریخته می‌شوند. Bazel همچنین از جعبه های شنی برای محدود کردن اقدامات از برقراری ارتباط از طریق شبکه استفاده می کند.

قطعی کردن وابستگی های خارجی

هنوز یک مشکل باقی مانده است: سیستم های بیلد اغلب به دانلود وابستگی ها (اعم از ابزارها یا کتابخانه ها) از منابع خارجی نیاز دارند تا اینکه مستقیماً آنها را بسازند. این را می توان در مثال از طریق وابستگی @com_google_common_guava_guava//jar کرد که یک فایل JAR را از Maven دانلود می کند.

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

مشکل اساسی این است که ما می خواهیم سیستم ساخت از این فایل ها بدون نیاز به بررسی آنها در کنترل منبع آگاه باشد. به روز رسانی یک وابستگی باید یک انتخاب آگاهانه باشد، اما این انتخاب باید یک بار در یک مکان مرکزی انجام شود نه اینکه توسط مهندسان فردی یا به طور خودکار توسط سیستم مدیریت شود. این به این دلیل است که حتی با یک مدل «Live at Head»، ما همچنان می‌خواهیم ساخت‌ها قطعی باشند، که به این معنی است که اگر یک commit هفته گذشته را بررسی کنید، باید وابستگی‌های خود را همانطور که در آن زمان بود ببینید نه اینکه الان هستند.

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

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

البته، اگر سرور راه دور از دسترس خارج شود یا شروع به ارائه داده های خراب کند، همچنان می تواند مشکل ساز باشد - اگر نسخه دیگری از آن وابستگی در دسترس نداشته باشید، می تواند باعث شود که تمام ساخت های شما شروع به شکست کنند. برای جلوگیری از این مشکل، توصیه می‌کنیم که برای هر پروژه غیر ضروری، تمام وابستگی‌های آن را به سرورها یا سرویس‌هایی که به آن‌ها اعتماد و کنترل می‌کنید منعکس کنید. در غیر این صورت، شما همیشه در اختیار شخص ثالثی برای در دسترس بودن سیستم ساخت خود خواهید بود، حتی اگر هش های ثبت شده امنیت آن را تضمین کنند.