نسخه‌ها و محدودیت‌ها در کامپوزر

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

نسخه‌ها در کامپوزر در مقابل نسخه‌ها در سیستم کنترل نسخه

از آنجا که کامپوزر به شدت به سمت استفاده از سیستم‌های کنترل نسخه مانند گیت (git) هدایت می‌شود، اصلاح نسخه می‌تواند کمی مبهم باشد. در مفهوم یک سیستم کنترل نسخه، یک نسخه مجموعه‌ای خاص از فایل‌هایی است که حاوی داده‌های خاص می‌باشند. در اصطلاحات فنی گیت، یک نسخه، یک مرجع یا یک کامیت خاص (که ممکن است توسط هِد (Head) یک شاخه (Branch) نشان داده شود) و یا یک برچسب است. وقتی از دستور checkout در سیستم کنترل نسخه خود (برای ارجاع و بررسی برچسب یا کامیت مورد نظرتان استفاده می‌کنید، برای مثال برچسب v1.1 یا کامیت  e35fa0d)، در واقع مجموعه‌ای از فایل‌ها را تحت نام واحد و مشخصی درخواست داده‌اید و در نتیجه همیشه فایل‌های همسان را دریافت خواهید نمود.

گیت (git)

گیت یک سیستم کنترل نسخه یعنی ابزاری برای کنترل کد منبع (Source Code) است، بدین معنی که کلیه تاریخچه تغییرات در کد منبع برنامه نوشته شده توسط شما را در خود نگهداری می‌کند و هر زمان بخواهید می‌توانید تغییرات را بررسی کنید و در صورت نیاز به نسخه‌های قبلی برگردید، این ابزار به شدت در پروژه‌های گروهی مفید است. کلیه اطلاعات مربوط به گیت در پوشه‌ای با نام قرار .git می‌گیرند.

اصطلاحات مهم گیت

  • مخزن (Repository): محل نگهداری کدها و تاریخچه و سوابق آن‌ها است، در واقع هر مخزن یک پروژه است.
  • شاخه (Branch): هر پروژه می‌تواند دارای چندین شاخه باشد و هر شاخه نیز چندین شاخه دیگر داشته باشد و هدف از شاخه‌ها توسعه و افزودن ویژگی‌هایی به برنامه است، شاخه‌ها می‌توانند با پروژه اصلی (شاخه Master) ادغام شوند و یا به مسیر جداگانه خود ادامه دهند.
  • کامیت (Commit): وقتی از این دستور استفاده کنیم، نسخه‌ای از تغییرات صورت گرفته روی کدهای پروژه در برنامه ذخیره می‌شود، البته این تغییرات در کامپیوتر شما بوده و برای انتقال تغییرات به مخزن در سرور اصلی، باید از دستور push استفاده کنید.
  • کلون (Clone): این دستور یک نسخه از مخزن را دانلود کرده و در سیستم شما قرار می‌دهد و در واقع شما یک شاخه از برنامه را دارید که می‌توانید توسعه دهید و با دستور push تغییرات کامیت شده را با مخزن ادغام کنید، توجه کنید که برای دریافت تغییرات جدید در مخزن باید از دستور pull استفاده نمایید.
  • برچسب (Tag): همانطور که مطالب مرتبط با کامپوزر در برچسبی با نام کامپوزر قرار دارد،شما نیز می‌توانید برای هر کامیت برچسبی اختصاص دهید تا راحت‌تر بتوانید به آن رجوع کنید.
  • هِد (Head): آخرین تغییرات بعد از کامیت کردن در هِد قرار می‌گیرد و برای انتقال تغییرات به مخزن در سرور اصلی، باید از دستور push استفاده کنید.
توجه: ادامه توضیحات درباره گیت خارج از حوصله این مطلب است، اگر تمایل دارید درباره گیت بیشتر بدانید، منتظر مطالب اختصاصی درباره گیت باشید.

در کامپوزر، چیزی که اغلب به یک شماره به عنوان یک نسخه اشاره دارد (رشته‌ای که نام پکیج را در خط حاوی دستور require دنبال می‌کند، برای مثال ~1.1  یا 1.2.*)، در واقع و به طور خاص یک محدودیت نسخه است. کامپوزر از محدودیت‌های نسخه برای تشخیص اینکه کدام نسخه را در سیستم کنترل نسخه باید بررسی کند، استفاده می‌کند (یا به زبان ساده‌تر برای تایید اینکه در مورد کتابخانه نگهداری شده به صورت ایستا، یک کتابخانه معین با مشخصات یک نسخه در فایل composer.json قابل قبول است.)

شاخه‌ها و برچسب‌های سیستم کنترل نسخه

برای شروع بحث در مورد این موضوع، مخزن کتابخانه ساده زیر را در نظر بگیرید:

~/my-library$ git branch
v1
v2
my-feature
nother-feature

~/my-library$ git tag
v1.0
v1.0.1
v1.0.2
v1.1-BETA
v1.1-RC1
v1.1-RC2
v1.1
v1.1.1
v2.0-BETA
v2.0-RC1
v2.0
v2.0.1
v2.0.2

برچسب‌ها (Tags)

به طور معمول کامپوزر با برچسب‌ها سروکار دارد (برخلاف شاخه‌ها). وقتی شما یک محدودیت نسخه را می‌نویسید، ممکن است به یک برچسب خاص (برای مثال 1.1) یا محدوده‌ای معتبر از برچسب‌ها (برای مثال >=1.1 <2.0 یا ~4.0) رجوع کنید. کامپوزر برای رفع این محدودیت‌ها، ابتدا لیست تمام برچسب‌های در دسترس را از سیستم کنترل نسخه درخواست می‌کند، سپس یک لیست داخلی از نسخه‌های در دسترس بر پایه این برچسب‌ها ایجاد می‌کند. در مثال بالا، لیست داخلی کامپوزر شامل نسخه‌های 1.0، 1.0.1، 1.0.2، ویرایش بتا نسخه 1.1، کاندیدهای اولین و دومین ویرایش 1.1، ویرایش  نسخه نهایی 1.1 و ... است. (دقت کنید که کامپوزر برای بدست آوردن یک شماره نسخه نهایی معتبر، به صورت خودکار پیشوند 'v' را از نام واقعی برچسب حذف می‌کند.) وقتی کامپوزر لیست کاملی از نسخه‌های در دسترس را با کمک سیستم کنترل نسخه شما داشته باشد، سپس به دنبال آخرین نسخه‌ای می‌گردد که با تمام محدودیت‌های ورژن در پروژه شما منطبق باشد (زیرا ممکن است که سایر پکیج ها به نسخه‌های مشخص از کتابخانه‌ای که شما استفاده می‌کنید نیاز داشته باشند، بنابراین نسخه‌ای که انتخاب می‌شود ممکن است همیشه آخرین نسخه در دسترس نباشد) و کامپوزر یک فایل فشرده (فایل آرشیو zip) از آن برچسب را دانلود می‌کند تا در موقعیت صحیحی در دایرکتوری vendor شما از حالت فشرده خارج کند.

شاخه‌ها (Branches)

اگر می‌خواهید که کامپوزر به جای یک برچسب، به یک شاخه رجوع کند، باید با استفاده از پیشوند (یا گاهی اوقات پسوند) خاص dev-* به آن شاخه اشاره کنید. اگر به یک شاخه رجوع کنید، کامپوزر فرض خواهد کرد که می‌خواهید روی آن شاخه کار کنید و در حقیقت یک کلون از مخزن را در مکانی صحیح در دایرکتوری vendor قرار می‌دهد. برای برچسب‌ها، در واقع فقط فایل‌های درست را بدون کلون کردن مخزن کپی می‌کند. (این رفتار را با دستورات --prefer-source و --prefer-dist می‌توانید اصلاح کنید، بزودی مطلبی در این باره مینویسم.) دوباره مثال بالا را ببینید، اگر شما می‌خواهید به شاخه my-feature رجوع کنید، باید dev-my-feature را به عنوان محدودیت نسخه در خط require مشخص کنید. نتیجه اینکار در کامپوزر کلون شدن مخزن my-library در دایرکتوری vendor و رجوع به شاخه my-feature است. وقتی نام‌های شاخه‌ها شبیه نسخه‌ها باشد، باید این موضوع را برای کامپوزر مشخص کنیم که داریم به یک شاخه رجوع می‌کنیم نه یک برچسب. در مثال بالا، ما دو شاخه نسخه داریم: v1 و v2. برای اینکه که به کامپوزر بگوییم که به یکی از این شاخه‌ها رجوع کند؛ باید یک محدودیت نسخه، چیزی شبیه v1.x-dev را مشخص کنیم. .x یک رشته قراردادی است که کامپوزر نیاز دارد تا بفهمد که منظور ما شاخه v1 است و نه برچسب v2 (به طور مشابه، شما می‌توانید شاخه را به جای v1.x ، v1 بنامید). در مورد یک شاخه با نامی شبیه نسخه (در این مورد، v1)، به جای استفاده از پیشوند dev- ، -dev را به عنوان یک پسوند اضافه می‌کنید.

حداقل پایداری (Minimum Stability)

هنوز چیز دیگری باقی مانده که فایل‌های بررسی شده سیستم کنترل نسخه یک کتابخانه و اضافه شده به پروژه شما را تحت تاثیر قرار می‌دهد: کامپوزرزاجازه می‌دهد تا محدودیت‌های پایداری را برای محدود کردن برچسب‌هایی که معتبر درنظر گرفته شده‌اند، مشخص کنید. دقت کنید که کتابخانه مثال بالا، قبل از ویرایش رسمی نهایی، یک ویرایش بتا و دو ویرایش کاندید برای نسخه 1.1 منتشر کرده. برای دربافت این نسخه‌ها باید از دستور composer install و composer update یا استفاده کنید، ما باید صراحتا به کامپوزر بگوییم که با ویرایش‌های کاندید و ویرایش‌های بتا مشکلی نداریم (و حتی ویرایش‌های آلفا، البته اگر آن‌ها را می‌خواهید). این کار با استفاده از مقداردهی minimum-stability در فایل composer.json و یا با استفاده از پرچم‌های پایداری در محدودیت‌های ورژن قایل انجام است. در این مورد بزودی بیشتر می‌نویسم.

نوشتن محدودیت‌های نسخه

حالا که می‌دانید که کامپوزر به نسخه‌ها چگونه می‌نگرد، بیایید درباره نحوه مشخص کردن محدودیت‌های نسخه خاص برای وابستگی‌های پروژه شما صحبت کنیم:

محدودیت نسخه دقیق

شما می‌توانید با مشخص کردن نسخه دقیق پکیج به کامپوزر بگویید تا فقط این نسخه را نصب کند. اگر وابستگی‌های دیگری نیاز به یک نسخه متفاوت داشت، solver در نهایت شکست خواهد خورد و همه پروسه‌های نصب و بروزرسانی متوقف می‌شود. مثال: 1.0.2

محدوده نسخه

با استفاده از عملگرهای مقایسه‌ای، می‌توانید یک محدوده از نسخه‌های مجاز را مشخص کنید. عملگرهای مجاز شامل >،  >=،  <،  <= و  != هستند. می‌توانید محدوده‌های چندگانه تعریف کنید. محدوده‌های جدا شده به وسیله یک فضای خالی (Space  ) یا کاما (Comma ,) مانند یک AND منطقی رفتار خواهند کرد. همچنین استفاده از پایپ دوگانه (دو علامت پایپ در کنار هم ||، پایپ: Shift + \) نیز مانند یک OR منطقی عمل خواهد نمود. AND اولویت بالاتری در اجرا نسبت به OR دارد.

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

شکست پس‌سازگاری

وقتی در ساخت نسخه جدید برنامه، محصول یا سیستم از فناوری‎ها یا زبان جدیدی استفاده شود و سیستم یا برنامه جدید بتواند بدون مشکل با نسخه قبلی کار کند، یعنی بتوان داده‌ها را مانند نسخه قبلی استفاده کرد، می‌گویند سیستم ویژگی پس‌سازگاری (Backward compatibility) یا سازگاری عقبرو (downward compatibility) دارد، اصلاح یک سیستم به روشی که ویژگی پس‌سازگاری در آن اجازه استفاده ندارد، اغلب شکست پس‌سازگاری (breaking Backwards Compatibility) گفته می‌شود. به عنوان مثال می‌توان نرم افزار ورد را مثال زد که ورژن‌های جدید مانند 2017 قادر به باز کردن و کار با فایل‌های ورژن 2003 (فایل‌های با پسوند doc) نیز است، یا درگاه‌هایی که برای USB 3.0 طراحی شده‌اند ولی با ورژن USB 2.0 و قدیمی‌تر هم می‌توان از آن‌ها استفاده کرد.

مثال‌ها:

  • >=1.0
  • >=1.0 <2.0
  • >=1.0 <1.1 || >=1.2

محدوده نسخه با خط تیره (-)

شامل مجموعه‌ای از نسخه‌ها. برخی از نسخه‌ها در سمت راست خود با یک wildcard (کاراکترهای خاصر مانند * و ?) کامل می‌شوند. برای مثال 1.0 - 2.0 معادل >=1.0.0 <2.1 است و 2.0 می‌شود 2.0.*. از طرف دیگر 1.0.0 - 2.1.0 معادل با >=1.0.0 <=2.1.0 است. مثال: 1.0 - 2.0

محدوده نسخه Wildcard

شما می‌توانید یک الگو را با استفاده از یک وایدکارد * مشخص کنید. 1.0.* معادل >=1.0 <1.1 است. مثال: 1.0.*

سایر عملگرهای انتشار مهم

محدوده نسخه تیلدا (~)

برای توضیح عملگر ~ (کلید Shift + `)، استفاده از مثال بهتر است: ~1.2 معادل >=1.2 <2.0.0 است، در حالی که ~1.2.3 معادل >=1.2.3 <1.3.0 است. همانطور که می‌بینید این عملگر اغلب برای پروژه های نسخه‌بندی به شیوه semantic versioning مفید است. یک کاربرد متداول می‌تواند نشانه گذاری حداقل ورژن فرعی باشد که پروژه شما به آن بستگی دارد، مانند ~1.2 (که هر نسخه بالاتری تا 2.0 را شامل می‌شود). از آنجایی که در تئوری نباید تا نسخه 2.0 هیچ شکست پس‌سازگاری وجود داشته باشد، به خوبی کار می‌کند. روش دیگر این است که با استفاده از عملگر تیلدا (~) حداقل نسخه را مشخص کنید، اما اجازه می‌دهد تا آخرین رقم مشخص شده افزایش یابد. مثال: ~1.2

توجه: گرچه به طور مشخص نسخه 2.0-beta.1 قبل از نسخه 2.0 است، اما یک محدودیت نسخه مانند ~1.2 نباید آن را نصب کند. همانطور که در بالا گفته شد، ~1.2 فقط به معنی آن است که .2 می‌تواند تغییر کند، اما بخش اصلی (Major) آن یعنی 1. ثابت است.
توجه: عملگر تیلدا ~ در  این رفتارش برای شماره نسخه اصلی، یک استثنا دارد. برای مثال ~1 مشابه ~1.0 است که به شماره بخش اصلی اجازه نخواهد داد تا به تلاش برای پس‌سازگاری ادامه دهد.

محدوده نسخه کَرِت (^)

عملگر ^ (کلید Shift + 6) رفتار بسیار مشابهی دارد اما به روش نسخه‌بندی سمانتیک نزدیکتر است و همیشه اجازه به بروزرسانی‌های بدون توقف می‌دهد. برای مثال ^1.2.3 معادل >=1.2.3 <2.0.0 است که نتیجه آن این است که هیچدام نسخه‌های منتشر شده تا 2.0 نباید شکست پس‌سازگاری داشته باشند. همچنین این عملگر برای نسخه‌های قبل از 1.0 نیز با در نظر گرفتن ایمنی عمل می‌کند و با ^0.3 رفتار مشابه >=0.3.0 <0.4.0 دارد. این عملگر، هنگامی است کد کتابخانه را می‌نویسید عملگر توصیه شده برای حداکثر قابلیت همکاری است. مثال: ^1.2.3

محدودیت‌های پایداری

اگر شما از محدودیتی استفاده می‌کنید که صراحتا یک پایداری را تعریف نمی‌کند، کامپوزر بسته به عملگر(های) استفاده شده، -dev یا -stable را به عنوان پیش‌فرض داخلی قرار می‌دهد. اگر تمایل دارید تا صراحتا فقط ویرایش‌های پایدار را مقایسه کنید، پسوند -stable را اضافه کنید. مثال‌ها:

محدودیت داخلی
1.2.3 =1.2.3.0-stable
>1.2 >1.2.0.0-stable
>=1.2 >=1.2.0.0-dev
>=1.2-stable >=1.2.0.0-stable
<1.3 <1.3.0.0-dev
<=1.3 <=1.3.0.0-stable
1 - 2 >=1.0.0.0-dev <3.0.0.0-dev
~1.3 >=1.3.0.0-dev <2.0.0.0-dev
1.4.* >=1.4.0.0-dev <1.5.0.0-dev

با این حال برای اجازه به پایداری‌های مختلف بدون مجبور کردن آن‌ها در سطح محدودیت، ممکن است از پرچم‌های پایداری مانند @<stability> (مثلا @dev) استفاده کنید تا به کامپوزر اجازه بدهید تا بداند که پکیج دریافتی می‌تواند در یک پایداری متفاوت از تنظیمات حداقل پایداری شما نصب شود. لیست کلیه پرچم‌های پایداری در دسترس به زودی در مطلبی دیگر قرار خواهند گرفت.

خلاصه

"require": {
    "vendor/package": "1.3.2", // exactly 1.3.2

    // >, <, >=, <= | specify upper / lower bounds
    "vendor/package": ">=1.3.2", // anything above or equal to 1.3.2
    "vendor/package": "<1.3.2", // anything below 1.3.2

    // * | wildcard
    "vendor/package": "1.3.*", // >=1.3.0 <1.4.0

    // ~ | allows last digit specified to go up
    "vendor/package": "~1.3.2", // >=1.3.2 <1.4.0
    "vendor/package": "~1.3", // >=1.3.0 <2.0.0

    // ^ | doesn't allow breaking changes (major version fixed - following semver)
    "vendor/package": "^1.3.2", // >=1.3.2 <2.0.0
    "vendor/package": "^0.3.2", // >=0.3.2 <0.4.0 // except if major version is 0
}

تست محدودیت‌های نسخه

با استفاده از semver.mwl.be می‌توانید محدودیت‌های نسخه را تست کنید. نام پکیج را پر کنید و این ابزار محدودیت نسخه پیش‌فرضی که کامپوزر به فایل composer.json شما اضافه کرده را به صورت خودکار پر می‌کند. شما می‌توانید محدودیت نسخه را تنظیم نموده و این ابزار آخرین ویرایش‌های هماهنگ را برجسته می‌کند.

مجتبی پاکزاد

مجتبی پاکزاد

حل مساله و چالش رو خیلی دوست دارم و رابطه خیلی خوبی با ریاضیات، برنامه‌نویسی و اقتصاد دارم. علاقه زیادی به هوش‌مصنوعی، یادگیری ماشین و موضوعات مرتبط دارم.

دیدگاه‌ها


ثبت دیدگاه