المهام
يعرض الرمز التالي ثلاث مهام Gradle ، وسنشرح الاختلافات بين هؤلاء الثلاثة لاحقًا.
Task MyTask {println "مرحبا ، العالم!" } Task MyTask {Dolast {println "Hello ، World!" }} Task MyTask << {println "Hello ، World!" }هدفي هو إنشاء مهمة تطبع "Hello ، World!" عندما ينفذ. عندما أنشأت المهمة لأول مرة ، خمنت أنه يجب أن تكتب مثل هذا:
Task MyTask {println "مرحبا ، العالم!" }الآن ، حاول تنفيذ هذا myTask ، وأدخل Gradle MyTask على سطر الأوامر ، وطباعته على النحو التالي:
المستخدم $ gradle mytask مرحبا ، العالم! : MyTask محدثة
هذه المهمة تبدو وكأنها تعمل. يطبع "مرحبا ، العالم!".
ومع ذلك ، فإنه ليس ما توقعناه في الواقع. دعونا نلقي نظرة على السبب. أدخل مهام Gradle في سطر الأوامر لعرض جميع المهام المتاحة.
المستخدم $ Gradle مهام مرحبا ، العالم! : المهام ------------------------------------------------------------ جميع المهام التي يمكن تشغيلها من Project Root ---------------------------------------------------- Build Setup Casks ----------------- init-initializes building building build a new Gradle. [احتضان] ..........
انتظر ، لماذا "مرحبا ، العالم!" مطبوعة؟ أردت فقط أن أرى المهام المتاحة ولم أقم بتنفيذ أي مهام مخصصة!
السبب في الواقع بسيط للغاية. لدى Gradle Task مرحلتان رئيسيتان في دورة حياتها: مرحلة التكوين ومرحلة التنفيذ.
ربما ليست كلماتي دقيقة للغاية ، ولكن هذا يمكن أن يساعدني حقًا في فهم المهام.
يجب على Gradle تكوين المهمة قبل تنفيذها. ثم السؤال هو ، كيف يمكنني معرفة الرمز في مهمتي التي يتم تنفيذها أثناء عملية التكوين والرمز الذي يتم تشغيله عند تنفيذ المهمة؟ الجواب هو أن الرمز في المستوى الأعلى للمهمة هو رمز التكوين ، مثل:
Task MyTask {def name = "pavel" // <- سيتم تنفيذ هذا السطر من الكود println "Hello ، World!"لهذا السبب عندما أقوم بتنفيذ مهام Gradle ، أقوم بطباعة "Hello ، World!" - لأنه يتم تنفيذ رمز التكوين. لكن هذا ليس التأثير الذي أريده ، أريد "مرحبًا ، عالم!" لطباعته فقط عندما أتصل بشكل صريح MyTask. لتحقيق هذا التأثير ، أسهل طريقة هي استخدام طريقة المهمة#dolast ().
Task MyTask {def text = 'hello ، world!' // تكوين مهمتي dolast {println text // يتم تنفيذ هذا عندما تسمى مهمتي}}الآن ، "مرحبا ، العالم!" سوف يطبع فقط عندما أقوم بتنفيذ Gradle MyTask. رائع ، الآن أعرف كيفية تكوين وجعل المهمة تفعل الشيء الصحيح. هناك سؤال آخر. في المثال الأولي ، ماذا يعني رمز المهمة الثالثة؟
Task MyTask2 << {println "مرحبًا ، العالم!" }هذا في الواقع مجرد نسخة سكر النحوية من Dolast. له نفس تأثير طريقة الكتابة التالية:
Task MyTask {dolast {println 'hello ، world!' // يتم تنفيذ هذا عندما تسمى مهمتي}}ومع ذلك ، يتم تنفيذ جميع الرموز بهذه الطريقة ولا يوجد جزء تكوين من الكود ، لذلك فهي أكثر ملاءمة لتلك المهام البسيطة التي لا تتطلب التكوين. بمجرد أن تحتاج مهمتك إلى تكوينها ، لا تزال بحاجة إلى استخدام إصدار Dolast.
قواعد
تتم كتابة البرامج النصية Gradle باللغة الرائعة. بناء جملة Groovy يشبه Java إلى حد ما ، وآمل أن تتمكن من قبوله.
إذا كنت معتادًا بالفعل على Groovy ، فيمكنك تخطي هذا الجزء.
هناك مفهوم مهم للغاية في مروعة تحتاج إلى فهم الإغلاق (الإغلاق)
الإغلاق
الإغلاق هو مفتاح فهمنا للفرد. الإغلاق عبارة عن كتلة منفصلة من التعليمات البرمجية التي يمكن أن تتلقى المعلمات أو قيم الإرجاع أو يتم تعيينها للمتغيرات. إنه مشابه للواجهة القابلة للاتصال في Java و Future ، كما أنها مثل مؤشر الوظيفة ، وهو أمر سهل الفهم. . .
المفتاح هو أنه سيتم تنفيذ هذا الرمز عند الاتصال به ، وليس عند إنشاءه. انظر مثال الإغلاق:
def myclosure = {println 'hello world!' } // تنفيذ closingclosure ()#الإخراج: مرحبا العالم!فيما يلي إغلاق يتلقى معلمات:
def myclosure = {string str -> println str} // تنفيذ closingclosure ('Hello World!')#الإخراج: Hello World!إذا كان الإغلاق يتلقى معلمة واحدة فقط ، فيمكنك استخدامها للإشارة إلى هذه المعلمة:
def myclosure = {println it} // تنفيذ closingclosure ('Hello World!')#الإخراج: Hello World!الإغلاق الذي يتلقى معلمات متعددة:
def myclosure = {string str ، int num -> println "$ str: $ num"} // تنفيذ convationyclosure ('my string' ، 21) #Output: سلسلة: 21بالإضافة إلى ذلك ، يكون نوع المعلمة اختياريًا ، ويمكن اختصار المثال أعلاه على النحو التالي:
def myclosure = {str ، num -> println "$ str: $ num"} // تنفيذ closingclosure ('my string' ، 21) #Output: سلسلةي: 21ما هو رائع هو أن المتغيرات من السياق الحالي يمكن استخدامها في الإغلاق. بشكل افتراضي ، السياق الحالي هو الفصل الذي تم فيه إنشاء الإغلاق:
def myvar = 'hello world!' def myclosure = {println myvar} myclosure ()#الإخراج: Hello World!نقطة أخرى رائعة هي أنه يمكن تغيير سياق الإغلاق ، عبر الإغلاق#setDelegate (). هذه الميزة مفيدة للغاية:
def myclosure = {println myvar} // أنا أشير إلى myvar من myClass classMyClass m = new myclass () myclosure.setDelegate (m) myclosure () class myclass {def myvar = 'hello from myclass!'}#الإخراج: مرحبًا من my my!كما ترون ، لا يوجد Myvar عند إنشاء الإغلاق. لا توجد مشكلة في ذلك لأنه عندما ننفذ الإغلاق ، يوجد Myvar في سياق الإغلاق. في هذا المثال. لأنني غيرت سياقها إلى M قبل تنفيذ الإغلاق ، يوجد Myvar.
تمرير الإغلاق كمعلمة
تتمثل ميزة الإغلاق في أنه يمكن نقلها إلى طرق مختلفة ، والتي يمكن أن تساعدنا في فصل منطق التنفيذ. في المثال السابق ، أظهرت كيفية تمرير الإغلاق إلى مثيل للفصل. أدناه سوف نلقي نظرة على الطرق المختلفة التي تتلقى الإغلاق كمعلمات:
1. تلقي معلمة واحدة فقط والمعلمة هي طريقة الإغلاق: mymethod (myclosure)
2. إذا كانت الطريقة تتلقى معلمة واحدة فقط ، يمكن حذف الأقواس: mymethod myclosure
3. يمكنك استخدام الإغلاق المضمّن: mymethod {println 'hello world'}
4. طريقة لتلقي معلمتين: MyMethod (Arg1 ، MyClosure)
5. على غرار 4 ، الإغلاق الفردي مضمّن: mymethod (arg1 ، {println 'hello world'})
6. إذا كانت المعلمة الأخيرة هي إغلاق ، فيمكن إخراجها من قوسين: mymethod (arg1) {println 'hello world'}
هنا أريد فقط أن أذكرك ما إذا كانت كتابة 3 و 6 تبدو مألوفة؟
مثال Gradle
الآن بعد أن فهمنا بناء الجملة الأساسي ، كيف نستخدمه في البرامج النصية Gradle؟ دعونا نلقي نظرة على المثال التالي:
BuildScript {repositories {jCenter ()} التبعيات {classpath 'com.android.tools.build:Gradle:1.2.3'} allprojects {repositories {jCenter ()}}}} بمجرد أن تعرف بناء جملة Groovy ، هل من السهل فهم المثال أعلاه؟
الأول هو طريقة buildscript ، والتي تتلقى إغلاقًا:
DEF BuildScript (إغلاق الإغلاق)
التالي هو طريقة AllProjects ، والتي تتلقى أيضًا معلمة إغلاق:
def allprojects (إغلاق الإغلاق)
الآخرون متشابهون. . .
يبدو الأمر أسهل بكثير الآن ، لكنني لا أفهم شيئًا واحدًا ، أي أين يتم تعريف هذه الطرق؟ الجواب هو المشروع
مشروع
هذا مفتاح لفهم البرامج النصية Gradle.
سيتم تفويض البيان في المستوى الأعلى من برنامج Build Script إلى مثيلات المشروع ، مما يدل على أن المشروع هو المكان الذي أبحث عنه بالضبط.
سيجد البحث عن طريقة BuildScript على صفحة مستند المشروع كتلة البرنامج النصي {} (كتلة البرنامج النصي). الخ ما هو بحق الجحيم كتلة البرنامج النصي؟ وفقا للوثائق:
كتلة البرنامج النصي هي طريقة لا تتلقى سوى الإغلاق كمعلمة. استمر في قراءة وثائق BuildScript. تقول الوثيقة أن المندوبين إلى: Scripthandler من BuildScript. وهذا يعني أننا نمرر الإغلاق إلى طريقة BuildScript ، وسياق التنفيذ النهائي هو Scripthandler. في المثال أعلاه ، تم تمرير إغلاقنا إلى BuildScript يدعو المستودعات (الإغلاق) والتبعيات (الإغلاق). نظرًا لأن الإغلاق قد عهد إلى Scripthandler ، فسوف نبحث عن أساليب التبعيات في Scripthandler.
تم العثور على تبعيات الفراغ (الإغلاق التكوين). وفقًا للوثائق ، يتم استخدام التبعيات لتكوين تبعيات البرنامج النصي. وكانت التبعيات في نهاية المطاف قد عهدت إلى التبعية.
رأيت مدى استخدام الدرجات على نطاق واسع. فهم العهد مهم للغاية.
كتل السيناريو
بشكل افتراضي ، يتم تعريف الكثير من كتل البرامج النصية مسبقًا في المشروع ، لكن المكون الإضافي Gradle يسمح لنا بتحديد كتل نصية جديدة لأنفسنا!
هذا يعني أنه إذا قمت بنشر بعض {...} على المستوى العلوي من البرنامج النصي للبناء ، ولكن لا يمكنك العثور على كتلة البرنامج النصي أو الطريقة في وثائق Gradle ، في معظم الحالات ، هذه بعض كتل البرنامج النصي المحددة في البرنامج المساعد.
كتلة البرنامج النصي Android
لنلقي نظرة على ملف تطبيق Android/Build.gradle الافتراضي:
تطبيق البرنامج المساعد: 'com.android.application'android {compilesDkversion 22 BuildToolSversion "22.0.1" DefaultConfig {ApplicationId "com.trickyandroid.testapp getDefaultProguardFile ('Proguard-Android.txt') ، 'proguard-rules.pro'}}}أمر المهمة
لقد لاحظت أن معظم المشكلات التي واجهتها عند استخدام Gradle ترتبط بترتيب تنفيذ المهمة. من الواضح أنه إذا كان بنويتي ستعمل بشكل أفضل إذا تم تنفيذ مهمتي في الوقت المناسب. دعونا نلقي نظرة فاحصة على كيفية تغيير ترتيب تنفيذ المهام.
تعتمد
أعتقد أن الطريقة الأكثر مباشرة لشرح الطريقة التي تعتمد بها على المهام الأخرى عند تنفيذ مهمتك هي استخدام طريقة Deponsson.
على سبيل المثال ، في السيناريو التالي ، هناك مهمة A موجودة بالفعل. نريد إضافة مهمة ب ، ويجب أن يكون تنفيذها بعد تنفيذ A:
هذا سيناريو بسيط للغاية ، على افتراض أن تعريف A و B هو كما يلي:
المهمة A << {println 'hello from a'} المهمة b << {println 'hello from b'} ما عليك سوى الاتصال بـ B.Dependentson A وهذا جيد.
هذا يعني أنه طالما قمت بتنفيذ المهمة ب ، فإن المهمة أ هي التي ستنفذها أولاً.
Paveldudka $ Gradle B: Ahello من A: Bhello من B
بالإضافة إلى ذلك ، يمكنك إعلان تبعياتها في منطقة تكوين المهمة:
المهمة A << {println 'hello from a'} Task B {reparson a dolast {println 'hello from b'}} ماذا لو أردنا إدراج مهمتنا في تبعية المهمة الحالية؟
العملية مشابهة لما الآن. افترض أن تبعيات المهمة التالية موجودة بالفعل:
المهمة A << {println 'hello from a'} المهمة b << {println 'hello from b'} Task C << {println 'hello from c'} b.dependentson ac.dependentson bانضم إلى مهمتنا الجديدة
المهمة B1 << {println 'hello from b1'} b1.dependson bc.dependson b1الإخراج:
Paveldudka $ Gradle C: Ahello من A: Bhello من B: B1Hello من B1: Chello from C.
لاحظ أن Depressson يضيف المهام إلى المجموعة التابعة ، لذلك لا توجد مشكلة في الاعتماد على مهام متعددة.
المهمة B1 << {println 'hello from b1'} b1.dependson bb1.dependson Qالإخراج:
Paveldudka $ Gradle B1: Ahello من A: Bhello من B: Qhello من Q: B1Hello من B1
Musstrunafter
لنفترض الآن أن لدي مهمة أخرى ، والتي تعتمد على المهمتين الأخريين. هنا أستخدم سيناريو حقيقي حيث لديّ مهمتان ، مهمة تم اختبارها وحدة واحدة ومهمة اختبار واجهة المستخدم. هناك أيضًا مهمة تدير جميع الاختبارات ، والتي تعتمد على المهمتين السابقتين.
وحدة المهمة << {println 'hello from unit unit'} task ui << {println 'hello from ui tests'} اختبارات المهمة << {println 'hello from all tests!'} tests.dependson unittests.dependson uiالإخراج:
اختبارات Paveldudka $ Gradle: Uihello من اختبارات واجهة المستخدم: Unithello من اختبارات الوحدة: TestShello من جميع الاختبارات!
على الرغم من أن اختبار UNSEST و UI سيتم تنفيذه قبل مهمة الاختبار ، لا يمكن ضمان ترتيب تنفيذ الوحدة وواجهة المستخدم. على الرغم من أنه يتم تنفيذه بترتيب الأبجدية الآن ، إلا أن هذا يعتمد على تنفيذ Gradle ، ويجب ألا تعتمد على هذا الطلب في الكود الخاص بك.
نظرًا لأن وقت اختبار واجهة المستخدم أطول بكثير من وقت اختبار الوحدة ، أريد تنفيذ اختبار الوحدة أولاً. أحد الحلول هو جعل مهمة واجهة المستخدم تعتمد على مهمة الوحدة.
وحدة المهمة << {println 'hello from unit unit tests'} task ui << {println 'hello from ui tests'} اختبارات المهمة << {println 'hello from all tests!'} tests.dependson unittests.dependson uiui.dependson unitالإخراج:
Paveldudka $ اختبارات Gradle: Unithello من اختبارات الوحدة: Uihello من اختبار واجهة المستخدم: Testshello من جميع الاختبارات!
الآن سيتم تنفيذ اختبار الوحدة قبل اختبار واجهة المستخدم.
ولكن هناك مشكلة مثيرة للاشمئزاز للغاية هنا. اختبار واجهة المستخدم الخاص بي في الواقع لا يعتمد على اختبار الوحدة. آمل أن أكون قادرًا على تنفيذ اختبار واجهة المستخدم بشكل منفصل ، لكن هنا في كل مرة أقوم فيها بتنفيذ اختبار واجهة المستخدم ، سأقوم بتنفيذ اختبار الوحدة أولاً.
هناك حاجة إلى Musstrunafter هنا. لا يضيف Mustrunafter تبعيات ، بل يخبر Gradle بأولوية التنفيذ إذا كانت هناك مهمتان في نفس الوقت. على سبيل المثال ، يمكننا تحديد وحدة ui.mustrunafter هنا. وبهذه الطريقة ، إذا كانت مهمة واجهة المستخدم ومهمة الوحدة موجودة في نفس الوقت ، فسيقوم Gradle أولاً بتنفيذ اختبار الوحدة ، وإذا تم تنفيذ واجهة المستخدم Gradle فقط ، فلن يتم تنفيذ مهمة الوحدة.
وحدة المهمة << {println 'hello from unit tests'} task ui << {println 'hello from ui tests'} اختبارات المهمة << {println 'hello from all tests!'} tests.dependson unittests.dependson uiui.mustrunafter unitالإخراج:
Paveldudka $ اختبارات Gradle: Unithello من اختبارات الوحدة: Uihello من اختبار واجهة المستخدم: Testshello من جميع الاختبارات!
علاقة التبعية على النحو التالي:
Musstrunafter هو حاليا ميزة تجريبية في Gradle 2.4.
وضع اللمسات الأخيرة
الآن لدينا مهمتان ، الوحدة وواجهة المستخدم ، على افتراض أن كلا المهمتين ستقومان بإخراج تقارير الاختبار ، وأريد الآن دمج هذين الاختبارين في واحد:
وحدة المهام << {println 'hello from unit unit tests'} Task ui << {println 'hello from ui tests'} اختبارات المهمة << {println 'hello from all tests!الآن إذا كنت أرغب في الحصول على تقارير اختبار لـ UI والوحدة ، قم بتنفيذ Mergereports Task.
Paveldudka $ gradle mergereports: unithello من اختبارات الوحدة: uihello من اختبارات واجهة المستخدم: Testshello من جميع الاختبارات!
هذه المهمة تعمل ، لكنها تبدو غبية للغاية. Mergereports لا تشعر بالرضا بشكل خاص من منظور المستخدم. أرغب في تنفيذ مهمة الاختبارات للحصول على تقرير الاختبار دون الحاجة إلى معرفة وجود Mergereports. بالطبع يمكنني نقل منطق الدمج إلى مهمة الاختبارات ، لكنني لا أريد أن أجعل مهمة الاختبارات منتفخة للغاية ، لذلك سأستمر في وضع منطق الدمج في مهمة Mergereports.
FinizeBy هنا لإنقاذ المشهد. كما يوحي الاسم ، فإن اللمسات الأخيرة هي المهمة التي سيتم تنفيذها بعد تنفيذ المهمة. قم بتعديل البرنامج النصي على النحو التالي:
وحدة المهام << {println 'hello from unit unit'} task ui << {println 'hello from ui tests'} اختبارات المهمة << {println 'hello from all tests!'} task mergereports << {println 'merging test reports. tests.finalbyby mergereportsقم الآن بتنفيذ مهمة الاختبارات ويمكنك الحصول على تقرير الاختبار:
اختبارات Paveldudka $ Gradle: Unithello من اختبارات الوحدة: Uihello من اختبارات واجهة المستخدم: Testshello من جميع الاختبارات!