نشر صندوق إلى Crates.io (Publishing a Crate to Crates.io)
لقد استخدمنا حزماً من crates.io كـ (تبعيات) dependencies لمشروعنا، ولكن يمكنك أيضاً مشاركة الكود الخاص بك مع أشخاص آخرين عن طريق نشر حزمك الخاصة. يقوم (سجل الصناديق) crate registry في crates.io بتوزيع الكود المصدري لحزمك، لذا فهو يستضيف بشكل أساسي الكود مفتوح المصدر.
تمتلك Rust و Cargo ميزات تجعل من السهل على الأشخاص العثور على حزمتك المنشورة واستخدامها. سنتحدث عن بعض هذه الميزات لاحقاً ثم نشرح كيفية نشر الحزمة.
كتابة تعليقات توثيقية مفيدة (Making Useful Documentation Comments)
سيساعد توثيق حزمك بدقة المستخدمين الآخرين على معرفة كيفية ووقت استخدامها، لذا يستحق الأمر استثمار الوقت في كتابة التوثيق. في الفصل الثالث، ناقشنا كيفية التعليق على كود Rust باستخدام شرطتين مائلتين، //. تمتلك Rust أيضاً نوعاً خاصاً من التعليقات للتوثيق، يُعرف بـ (تعليق التوثيق) documentation comment ، والذي سيقوم بإنشاء توثيق HTML. يعرض HTML محتويات تعليقات التوثيق لعناصر (واجهة برمجة التطبيقات العامة) public API المخصصة للمبرمجين المهتمين بمعرفة كيفية استخدام (الصندوق) crate الخاص بك بدلاً من كيفية تنفيذه.
تستخدم تعليقات التوثيق ثلاث شرطات مائلة، /// ، بدلاً من اثنتين وتدعم تنسيق Markdown لتنسيق النص. ضع تعليقات التوثيق مباشرة قبل العنصر الذي تقوم بتوثيقه. تعرض القائمة 14-1 تعليقات التوثيق لدالة add_one في صندوق يسمى my_crate.
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
هنا، نقدم وصفاً لما تفعله دالة add_one ، ونبدأ قسماً بالعنوان Examples ، ثم نقدم كوداً يوضح كيفية استخدام دالة add_one. يمكننا إنشاء توثيق HTML من تعليق التوثيق هذا عن طريق تشغيل cargo doc. يقوم هذا الأمر بتشغيل أداة rustdoc الموزعة مع Rust ويضع توثيق HTML المنشأ في دليل target/doc.
للراحة، سيؤدي تشغيل cargo doc --open إلى بناء HTML لتوثيق الصندوق الحالي (بالإضافة إلى توثيق جميع تبعيات الصندوق الخاص بك) وفتح النتيجة في متصفح الويب. انتقل إلى دالة add_one وسترى كيف يتم عرض النص في تعليقات التوثيق، كما هو موضح في الشكل 14-1.
الشكل 14-1: توثيق HTML لدالة add_one
الأقسام شائعة الاستخدام (Commonly Used Sections)
استخدمنا عنوان Markdown باسم # Examples في القائمة 14-1 لإنشاء قسم في HTML بعنوان “Examples”. إليك بعض الأقسام الأخرى التي يشيع استخدامها من قبل مؤلفي الصناديق في توثيقاتهم:
- Panics: هذه هي السيناريوهات التي يمكن أن تؤدي فيها الدالة التي يتم توثيقها إلى (ذعر)
panic. يجب على مستدعي الدالة الذين لا يريدون لبرامجهم أن تصاب بالذعر التأكد من عدم استدعاء الدالة في هذه الحالات. - Errors: إذا كانت الدالة تعيد
Result، فإن وصف أنواع الأخطاء التي قد تحدث والظروف التي قد تتسبب في إعادة تلك الأخطاء يمكن أن يكون مفيداً للمستدعين حتى يتمكنوا من كتابة كود للتعامل مع أنواع الأخطاء المختلفة بطرق مختلفة. - Safety: إذا كانت الدالة (غير آمنة)
unsafeللاستدعاء (نناقش عدم الأمان في الفصل 20)، فيجب أن يكون هناك قسم يشرح سبب كون الدالة غير آمنة ويغطي (الثوابت)invariantsالتي تتوقع الدالة من المستدعين الحفاظ عليها.
لا تحتاج معظم تعليقات التوثيق إلى كل هذه الأقسام، ولكن هذه قائمة مرجعية جيدة لتذكيرك بجوانب الكود التي سيهتم المستخدمون بمعرفتها.
تعليقات التوثيق كاختبارات (Documentation Comments as Tests)
يمكن أن يساعد إضافة كتل كود برمجية كمثال في تعليقات التوثيق الخاصة بك في توضيح كيفية استخدام مكتبتك وله ميزة إضافية: سيؤدي تشغيل cargo test إلى تشغيل أمثلة الكود في توثيقك كاختبارات! لا شيء أفضل من التوثيق مع الأمثلة. ولكن لا شيء أسوأ من الأمثلة التي لا تعمل لأن الكود قد تغير منذ كتابة التوثيق. إذا قمنا بتشغيل cargo test مع توثيق دالة add_one من القائمة 14-1، فسنرى قسماً في نتائج الاختبار يبدو كالتالي:
Doc-tests my_crate
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
الآن، إذا قمنا بتغيير الدالة أو المثال بحيث يؤدي assert_eq! في المثال إلى panic ، وقمنا بتشغيل cargo test مرة أخرى، فسنرى أن (اختبارات التوثيق) doc tests تكتشف أن المثال والكود غير متوافقين مع بعضهما البعض!
تعليقات العناصر المحتواة (Contained Item Comments)
يضيف نمط تعليق التوثيق //! توثيقاً للعنصر الذي يحتوي على التعليقات بدلاً من العناصر التي تلي التعليقات. نستخدم عادةً تعليقات التوثيق هذه داخل ملف جذر الصندوق (src/lib.rs حسب العرف) أو داخل (وحدة) module لتوثيق الصندوق أو الوحدة ككل.
على سبيل المثال، لإضافة توثيق يصف الغرض من الصندوق my_crate الذي يحتوي على دالة add_one ، نضيف تعليقات توثيق تبدأ بـ //! إلى بداية ملف src/lib.rs ، كما هو موضح في القائمة 14-2.
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
لاحظ أنه لا يوجد أي كود بعد السطر الأخير الذي يبدأ بـ //!. لأننا بدأنا التعليقات بـ //! بدلاً من /// ، فإننا نوثق العنصر الذي يحتوي على هذا التعليق بدلاً من العنصر الذي يلي هذا التعليق. في هذه الحالة، هذا العنصر هو ملف src/lib.rs ، وهو جذر الصندوق. تصف هذه التعليقات الصندوق بأكمله.
عندما نشغل cargo doc --open ، ستظهر هذه التعليقات في الصفحة الأولى من التوثيق لـ my_crate فوق قائمة العناصر العامة في الصندوق، كما هو موضح في الشكل 14-2.
تعليقات التوثيق داخل العناصر مفيدة لوصف الصناديق والوحدات بشكل خاص. استخدمها لشرح الغرض العام للحاوية لمساعدة مستخدميك على فهم تنظيم الصندوق.
الشكل 14-2: التوثيق المعروض لـ my_crate ، بما في ذلك التعليق الذي يصف الصندوق ككل
تصدير واجهة برمجة تطبيقات عامة مريحة (Exporting a Convenient Public API)
يعد هيكل public API الخاص بك اعتباراً رئيسياً عند نشر صندوق. الأشخاص الذين يستخدمون صندوقك أقل دراية بالهيكل منك وقد يجدون صعوبة في العثور على الأجزاء التي يريدون استخدامها إذا كان لصندوقك (تسلسل هرمي للوحدات) module hierarchy كبير.
في الفصل السابع، غطينا كيفية جعل العناصر عامة باستخدام الكلمة المفتاحية pub ، وكيفية جلب العناصر إلى النطاق باستخدام الكلمة المفتاحية use. ومع ذلك، فإن الهيكل الذي يبدو منطقياً بالنسبة لك أثناء تطوير الصندوق قد لا يكون مريحاً جداً لمستخدميك. قد ترغب في تنظيم (هياكلك) structs في تسلسل هرمي يحتوي على مستويات متعددة، ولكن بعد ذلك قد يواجه الأشخاص الذين يريدون استخدام نوع قمت بتعريفه في عمق التسلسل الهرمي صعوبة في اكتشاف وجود هذا النوع. قد ينزعجون أيضاً من الاضطرار إلى كتابة use my_crate::some_module::another_module::UsefulType; بدلاً من use my_crate::UsefulType;.
الخبر السار هو أنه إذا كان الهيكل ليس مريحاً للآخرين لاستخدامه من مكتبة أخرى، فليس عليك إعادة ترتيب تنظيمك الداخلي: بدلاً من ذلك، يمكنك (إعادة تصدير) re-export العناصر لإنشاء هيكل عام يختلف عن هيكلك الخاص باستخدام pub use. تأخذ عملية إعادة التصدير عنصراً عاماً في مكان ما وتجعله عاماً في مكان آخر، كما لو كان قد تم تعريفه في المكان الآخر بدلاً من ذلك.
على سبيل المثال، لنفترض أننا صنعنا مكتبة باسم art لنمذجة المفاهيم الفنية. داخل هذه المكتبة توجد وحدتان: وحدة kinds تحتوي على (تعدادين) enums باسم PrimaryColor و SecondaryColor ووحدة utils تحتوي على دالة باسم mix ، كما هو موضح في القائمة 14-3.
//! # Art
//!
//! A library for modeling artistic concepts.
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
unimplemented!();
}
}
يوضح الشكل 14-3 كيف ستبدو الصفحة الأولى من التوثيق لهذا الصندوق المنشأ بواسطة cargo doc.
الشكل 14-3: الصفحة الأولى من التوثيق لـ art التي تسرد وحدات kinds و utils
لاحظ أن الأنواع PrimaryColor و SecondaryColor ليست مدرجة في الصفحة الأولى، ولا دالة mix. علينا النقر فوق kinds و utils لرؤيتها.
سيحتاج صندوق آخر يعتمد على هذه المكتبة إلى عبارات use تجلب العناصر من art إلى النطاق، مع تحديد هيكل الوحدة المعرف حالياً. تعرض القائمة 14-4 مثالاً لصندوق يستخدم العناصر PrimaryColor و mix من صندوق art.
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
كان على مؤلف الكود في القائمة 14-4، الذي يستخدم صندوق art ، أن يكتشف أن PrimaryColor موجود في وحدة kinds وأن mix موجود في وحدة utils. هيكل وحدة صندوق art أكثر صلة بالمطورين الذين يعملون على صندوق art منه لأولئك الذين يستخدمونه. لا يحتوي الهيكل الداخلي على أي معلومات مفيدة لشخص يحاول فهم كيفية استخدام صندوق art ، بل يسبب ارتباكاً لأن المطورين الذين يستخدمونه يضطرون إلى اكتشاف مكان البحث، ويجب عليهم تحديد أسماء الوحدات في عبارات use.
لإزالة التنظيم الداخلي من public API ، يمكننا تعديل كود صندوق art في القائمة 14-3 لإضافة عبارات pub use لإعادة تصدير العناصر في المستوى الأعلى، كما هو موضح في القائمة 14-5.
//! # Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
// --snip--
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
// --snip--
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Orange
}
}
سيقوم توثيق API الذي ينشئه cargo doc لهذا الصندوق الآن بإدراج وربط عمليات إعادة التصدير في الصفحة الأولى، كما هو موضح في الشكل 14-4، مما يجعل الأنواع PrimaryColor و SecondaryColor ودالة mix أسهل في العثور عليها.
الشكل 14-4: الصفحة الأولى من التوثيق لـ art التي تسرد عمليات إعادة التصدير
لا يزال بإمكان مستخدمي صندوق art رؤية واستخدام الهيكل الداخلي من القائمة 14-3 كما هو موضح في القائمة 14-4، أو يمكنهم استخدام الهيكل الأكثر راحة في القائمة 14-5، كما هو موضح في القائمة 14-6.
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
في الحالات التي توجد فيها العديد من الوحدات المتداخلة، يمكن أن يؤدي إعادة تصدير الأنواع في المستوى الأعلى باستخدام pub use إلى إحداث فرق كبير في تجربة الأشخاص الذين يستخدمون الصندوق. استخدام شائع آخر لـ pub use هو إعادة تصدير تعريفات تبعية في الصندوق الحالي لجعل تعريفات ذلك الصندوق جزءاً من public API لصندوقك.
إن إنشاء هيكل public API مفيد هو فن أكثر منه علم، ويمكنك التكرار للعثور على API الذي يعمل بشكل أفضل لمستخدميك. يمنحك اختيار pub use مرونة في كيفية هيكلة صندوقك داخلياً ويفصل ذلك الهيكل الداخلي عما تقدمه لمستخدميك. انظر إلى بعض أكواد الصناديق التي قمت بتثبيتها لترى ما إذا كان هيكلها الداخلي يختلف عن public API الخاص بها.
إعداد حساب Crates.io (Setting Up a Crates.io Account)
قبل أن تتمكن من نشر أي صناديق، تحتاج إلى إنشاء حساب على crates.io والحصول على (رمز API) API token. للقيام بذلك، قم بزيارة الصفحة الرئيسية في crates.io وقم بتسجيل الدخول عبر حساب GitHub. (حساب GitHub هو مطلب حالي، ولكن قد يدعم الموقع طرقاً أخرى لإنشاء حساب في المستقبل.) بمجرد تسجيل الدخول، قم بزيارة إعدادات حسابك في https://crates.io/me/ واسترجع مفتاح API الخاص بك. بعد ذلك، قم بتشغيل أمر cargo login والصق مفتاح API الخاص بك عند مطالبتك بذلك، هكذا:
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
سيقوم هذا الأمر بإبلاغ Cargo برمز API الخاص بك وتخزينه محلياً في ~/.cargo/credentials.toml. لاحظ أن هذا الرمز هو سر: لا تشاركه مع أي شخص آخر. إذا قمت بمشاركته مع أي شخص لأي سبب من الأسباب، فيجب عليك إلغاؤه وإنشاء رمز جديد على crates.io.
إضافة البيانات الوصفية إلى صندوق جديد (Adding Metadata to a New Crate)
لنفترض أن لديك صندوقاً تريد نشره. قبل النشر، ستحتاج إلى إضافة بعض (البيانات الوصفية) metadata في قسم [package] من ملف Cargo.toml الخاص بالصندوق.
سيحتاج صندوقك إلى اسم فريد. بينما تعمل على صندوق محلياً، يمكنك تسمية الصندوق بأي اسم تريده. ومع ذلك، يتم تخصيص أسماء الصناديق على crates.io على أساس الأسبقية. بمجرد أخذ اسم الصندوق، لا يمكن لأي شخص آخر نشر صندوق بهذا الاسم. قبل محاولة نشر صندوق، ابحث عن الاسم الذي تريد استخدامه. إذا كان الاسم مستخدماً، فستحتاج إلى العثور على اسم آخر وتعديل حقل name في ملف Cargo.toml تحت قسم [package] لاستخدام الاسم الجديد للنشر، هكذا:
اسم الملف: Cargo.toml
[package]
name = "guessing_game"
حتى لو اخترت اسماً فريداً، عندما تقوم بتشغيل cargo publish لنشر الصندوق في هذه المرحلة، ستحصل على تحذير ثم خطأ:
$ cargo publish
Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip--
error: failed to publish to registry at https://crates.io
Caused by:
the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields
يؤدي هذا إلى حدوث خطأ لأنك تفتقد لبعض المعلومات المهمة: الوصف والترخيص مطلوبان حتى يعرف الناس ما يفعله صندوقك وتحت أي شروط يمكنهم استخدامه. في Cargo.toml ، أضف وصفاً يتكون من جملة أو جملتين فقط، لأنه سيظهر مع صندوقك في نتائج البحث. بالنسبة لحقل license ، تحتاج إلى تقديم (قيمة معرف الترخيص) license identifier value. يسرد تبادل بيانات حزمة البرامج (SPDX) التابع لمؤسسة Linux المعرفات التي يمكنك استخدامها لهذه القيمة. على سبيل المثال، لتحديد أنك قمت بترخيص صندوقك باستخدام ترخيص MIT، أضف معرف MIT:
اسم الملف: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
إذا كنت تريد استخدام ترخيص لا يظهر في SPDX، فأنت بحاجة إلى وضع نص ذلك الترخيص في ملف، وتضمين الملف في مشروعك، ثم استخدام license-file لتحديد اسم ذلك الملف بدلاً من استخدام مفتاح license.
الإرشاد بشأن الترخيص المناسب لمشروعك خارج نطاق هذا الكتاب. يقوم العديد من الأشخاص في مجتمع Rust بترخيص مشاريعهم بنفس طريقة Rust باستخدام ترخيص مزدوج من MIT OR Apache-2.0. توضح هذه الممارسة أنه يمكنك أيضاً تحديد معرفات ترخيص متعددة مفصولة بـ OR للحصول على تراخيص متعددة لمشروعك.
مع وجود اسم فريد، والإصدار، ووصفك، وإضافة ترخيص، قد يبدو ملف Cargo.toml لمشروع جاهز للنشر كالتالي:
اسم الملف: Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
يصف توثيق Cargo البيانات الوصفية الأخرى التي يمكنك تحديدها لضمان تمكن الآخرين من اكتشاف واستخدام صندوقك بسهولة أكبر.
النشر إلى Crates.io (Publishing to Crates.io)
الآن بعد أن أنشأت حساباً، وحفظت رمز API الخاص بك، واخترت اسماً لصندوقك، وحددت البيانات الوصفية المطلوبة، فأنت جاهز للنشر! يؤدي نشر الصندوق إلى رفع إصدار محدد إلى crates.io ليستخدمه الآخرون.
كن حذراً، لأن النشر (دائم) permanent. لا يمكن أبداً الكتابة فوق الإصدار، ولا يمكن حذف الكود إلا في ظروف معينة. أحد الأهداف الرئيسية لـ Crates.io هو العمل كأرشيف دائم للكود بحيث تستمر عمليات بناء جميع المشاريع التي تعتمد على صناديق من crates.io في العمل. السماح بحذف الإصدارات سيجعل تحقيق هذا الهدف مستحيلاً. ومع ذلك، لا يوجد حد لعدد إصدارات الصناديق التي يمكنك نشرها.
قم بتشغيل أمر cargo publish مرة أخرى. يجب أن ينجح الآن:
$ cargo publish
Updating crates.io index
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Packaged 6 files, 1.2KiB (895.0B compressed)
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
Uploaded guessing_game v0.1.0 to registry `crates-io`
note: waiting for `guessing_game v0.1.0` to be available at registry
`crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
Published guessing_game v0.1.0 at registry `crates-io`
تهانينا! لقد شاركت الآن الكود الخاص بك مع مجتمع Rust، ويمكن لأي شخص بسهولة إضافة صندوقك كتبعية لمشروعه.
نشر إصدار جديد من صندوق موجود (Publishing a New Version of an Existing Crate)
عندما تجري تغييرات على صندوقك وتكون جاهزاً لإصدار نسخة جديدة، فإنك تقوم بتغيير قيمة version المحددة في ملف Cargo.toml الخاص بك وتعيد النشر. استخدم قواعد إصدارات البرمجيات الدلالية (Semantic Versioning) لتقرر ما هو رقم الإصدار التالي المناسب، بناءً على أنواع التغييرات التي أجريتها. ثم، قم بتشغيل cargo publish لرفع الإصدار الجديد.
إهمال الإصدارات من Crates.io (Deprecating Versions from Crates.io)
على الرغم من أنه لا يمكنك إزالة الإصدارات السابقة من الصندوق، إلا أنه يمكنك منع أي مشاريع مستقبلية من إضافتها كتبعية جديدة. هذا مفيد عندما يكون إصدار الصندوق معطلاً لسبب أو لآخر. في مثل هذه الحالات، يدعم Cargo (سحب) yanking إصدار الصندوق.
يؤدي سحب الإصدار إلى منع المشاريع الجديدة من الاعتماد على ذلك الإصدار مع السماح لجميع المشاريع الحالية التي تعتمد عليه بالاستمرار. أساساً، يعني السحب أن جميع المشاريع التي تحتوي على ملف Cargo.lock لن تتعطل، وأي ملفات Cargo.lock يتم إنشاؤها في المستقبل لن تستخدم الإصدار المسحوب.
لسحب إصدار من صندوق، في دليل الصندوق الذي قمت بنشره مسبقاً، قم بتشغيل cargo yank وحدد الإصدار الذي تريد سحبه. على سبيل المثال، إذا قمنا بنشر صندوق باسم guessing_game إصدار 1.0.1 وأردنا سحبه، فسنقوم بتشغيل ما يلي في دليل المشروع لـ guessing_game:
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
عن طريق إضافة --undo إلى الأمر، يمكنك أيضاً التراجع عن السحب والسماح للمشاريع بالبدء في الاعتماد على إصدار مرة أخرى:
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
عملية السحب لا تحذف أي كود. لا يمكنها، على سبيل المثال، حذف أسرار تم رفعها عن طريق الخطأ. إذا حدث ذلك، يجب عليك إعادة تعيين تلك الأسرار فوراً.