Kotlin Native: تتبع الملفات

عندما تكتب أداة مساعدة لسطر الأوامر ، فإن آخر شيء تريد الاعتماد عليه هو تثبيت JVM أو Ruby أو Python على الكمبيوتر حيث سيتم تشغيله. أرغب أيضًا في امتلاك ملف ثنائي واحد يسهل تشغيله. ولا تهتم كثيرا بإدارة الذاكرة.

للأسباب المذكورة أعلاه ، في السنوات الأخيرة ، كلما احتجت إلى كتابة هذه الأدوات المساعدة ، كنت أستخدم Go.

يحتوي Go على بناء جملة بسيط نسبيًا ومكتبة قياسية جيدة ، وهناك مجموعة من البيانات المهملة ، وفي الإخراج نحصل على ثنائي واحد. يبدو أن ما هو مطلوب آخر؟

منذ وقت ليس ببعيد ، بدأ Kotlin أيضًا بتجربة نفسه في حقل مماثل في شكل Kotlin Native. بدا الاقتراح واعدًا - GC ، بناء جملة مفرد ومألوف وملائم. ولكن هل كل شيء جيد كما نود؟

المشكلة التي يتعين علينا حلها: كتابة مراقب ملفات بسيط على Kotlin Native. كحجج ، يجب أن تحصل الأداة على مسار الملف وتكرار الفحص. إذا تم تغيير الملف ، يجب أن تقوم الأداة المساعدة بإنشاء نسخة منه في نفس المجلد بالاسم الجديد.

بمعنى آخر ، يجب أن تبدو الخوارزمية كما يلي:

fileToWatch = getFileToWatch() howOftenToCheck = getHowOftenToCheck() while (!stopped) { if (hasChanged(fileToWatch)) { copyAside(fileToWatch) } sleep(howOftenToCheck) } 

حسنًا ، إذن ما نريد تحقيقه يبدو أنه تم تسويته. الوقت لكتابة الرمز.

الأربعاء


أول شيء نحتاجه هو IDE. عشاق فيم من فضلك لا تقلق.

نطلق IntelliJ IDEA المألوفة ونجد أنه في Kotlin Native لا يمكن أن ينطلق من الكلمة على الإطلاق. تحتاج إلى استخدام CLion .

لم تنته بعد مغامرات الشخص الذي واجه آخر مرة في عام 2004. تحتاج إلى toolchain. إذا كنت تستخدم OSX ، فمن المرجح أن تجد CLion سلسلة الأدوات المناسبة نفسها. ولكن إذا قررت استخدام Windows ولم تقم بالبرمجة في C ، فسوف يتعين عليك العبث بالبرنامج التعليمي لتثبيت بعض Cygwin .

تثبيت IDE ، وفرزها toolchain. هل يمكنني بالفعل بدء كتابة التعليمات البرمجية؟ تقريبا.
نظرًا لأن Kotlin Native لا يزال تجريبيًا إلى حد ما ، لم يتم تثبيت المكون الإضافي له في CLion افتراضيًا. لذلك قبل أن نرى النقش المحبب "New Kotlin / Native Application" سيتعين عليه تثبيته يدويًا .

بعض الإعدادات


وهكذا ، أخيرًا لدينا مشروع Kotlin Native فارغ. ومن المثير للاهتمام ، أنه يعتمد على Gradle (وليس على Makefiles) ، وحتى على إصدار Kotlin Script.

build.gradle.kts نظرة على build.gradle.kts :

 plugins { id("org.jetbrains.kotlin.konan") version("0.8.2") } konanArtifacts { program("file_watcher") } 

يسمى البرنامج المساعد الوحيد الذي سنستخدمه كونان. سوف ينتج ملفنا الثنائي.

في konanArtifacts نحدد اسم الملف القابل للتنفيذ. في هذا المثال ، نحصل على file_watcher.kexe

كود


لقد حان الوقت لإظهار الكود بالفعل. ها هي ، بالمناسبة:

 fun main(args: Array<String>) { if (args.size != 2) { return println("Usage: file_watcher.kexe <path> <interval>") } val file = File(args[0]) val interval = args[1].toIntOrNull() ?: 0 require(file.exists()) { "No such file: $file" } require(interval > 0) { "Interval must be positive" } while (true) { // We should do something here } } 

عادةً ما تحتوي الأدوات المساعدة لسطر الأوامر أيضًا على وسائط اختيارية وقيمها الافتراضية. ولكن على سبيل المثال ، سوف نفترض أن هناك دائمًا حجة اثنين: path interval

بالنسبة لأولئك الذين عملوا بالفعل مع Kotlin ، قد يبدو غريباً أن هذا path يلتف في فئة File الخاصة به ، دون استخدام java.io.File . التفسير لذلك في دقيقة أو دقيقتين.

إذا لم تكن معتادًا على الدالة require () في Kotlin ، فهذه مجرد طريقة أكثر ملاءمة للتحقق من صحة الحجج. Kotlin - كل شيء عن الراحة. يمكن للمرء أن يكتب مثل هذا:

 if (interval <= 0) { println("Interval must be positive") return } 

بشكل عام ، هنا هو رمز Kotlin المعتادة ، لا شيء مثير للاهتمام. ولكن من الآن فصاعدا سيكون متعة.

دعونا نحاول كتابة كود Kotlin العادي ، ولكن في كل مرة نحتاج فيها إلى استخدام شيء ما من Java ، نقول "عفوا!". هل انت جاهز

دعنا نرجع إلى وقتنا ، ونتركه يطبع كل interval برمز ، على سبيل المثال ، فترة.

 var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") // ... Thread.sleep(interval * 1000) } 

Thread هو فئة من Java. لا يمكننا استخدام فئات Java في Kotlin Native. فقط دروس Kotlin. لا جافا.

بالمناسبة ، لأننا في main لم نستخدم java.io.File

حسنًا ، ما الذي يمكن استخدامه بعد ذلك؟ وظائف من C!

 var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") sleep(interval) } 

مرحبا بكم في العالم جيم


الآن بعد أن علمنا بما ينتظرنا ، دعونا نرى كيف تبدو الوظيفة الحالية exists() من File :

 data class File(private val filename: String) { fun exists(): Boolean { return access(filename, F_OK) != -1 } // More functions... } 

File عبارة عن data class بسيطة ، مما يتيح لنا تنفيذ toString() من المربع ، والذي toString() لاحقًا.

"تحت الغطاء" ، ندعو وظيفة access() C ، التي تُرجع -1 إذا كان هذا الملف غير موجود.

مزيد من أسفل القائمة لدينا وظيفة modified() :

 fun modified(): Long = memScoped { val result = alloc<stat>() stat(filename, result.ptr) result.st_mtimespec.tv_sec } 

يمكن تبسيط الوظيفة قليلاً باستخدام نوع الاستدلال ، لكنني قررت هنا عدم القيام بذلك ، بحيث كان من الواضح أن الوظيفة لا تُرجع ، على سبيل المثال ، Boolean .

هناك نوعان من التفاصيل المثيرة للاهتمام في هذه الوظيفة. أولاً ، نستخدم alloc() . نظرًا لأننا نستخدم C ، نحتاج أحيانًا إلى تخصيص هياكل ، ويتم ذلك في C يدويًا ، باستخدام malloc ().

يجب أيضًا تحرير هذه الهياكل يدويًا. memScoped() وظيفة memScoped() من Kotlin Native في عملية memScoped() ، والتي ستقوم بذلك من أجلنا.

يبقى لنا أن نأخذ في الاعتبار الوظيفة الأكثر أهمية: opyAside()

 fun copyAside(): String { val state = copyfile_state_alloc() val copied = generateFilename() if (copyfile(filename, copied, state, COPYFILE_DATA) < 0) { println("Unable to copy file $filename -> $copied") } copyfile_state_free(state) return copied } 

هنا نستخدم دالة C copyfile_state_alloc() ، التي تحدد البنية اللازمة لـ copyfile() .

لكن علينا أن نطلق هذا الهيكل بأنفسنا - باستخدام
copyfile_state_free(state)

آخر ما تبقى لإظهار هو جيل الأسماء. هناك القليل من Kotlin:

 private var count = 0 private val extension = filename.substringAfterLast(".") private fun generateFilename() = filename.replace(extension, "${++count}.$extension") 

هذا رمز جميل ساذج يتجاهل العديد من الحالات ، لكنه سيفعل ذلك على سبيل المثال.

ابدأ


الآن كيفية تشغيل كل شيء؟

خيار واحد هو بالطبع استخدام CLion. سوف يفعل كل شيء من أجلنا.

ولكن دعونا نستخدم سطر الأوامر بدلاً من ذلك لفهم العملية بشكل أفضل. نعم ، وبعض CI لن يعمل كودنا من CLion.

 ./gradlew build && ./build/konan/bin/macos_x64/file_watcher.kexe ./README.md 1 

بادئ ذي بدء ، نقوم بتجميع مشروعنا باستخدام Gradle. إذا سارت الأمور على ما يرام ، فستظهر الرسالة التالية:

 BUILD SUCCESSFUL in 16s 

ستة عشر ثانية؟! نعم ، بالمقارنة مع بعض Go أو حتى Kotlin لـ JVM ، فإن النتيجة مخيبة للآمال. وهذا لا يزال مشروع صغير.

يجب أن تشاهد الآن النقاط التي تعمل عبر الشاشة. وإذا قمت بتغيير محتويات الملف ، فستظهر رسالة. شيء مثل هذه الصورة:

 ................................ File copied: ./README.1.md ................... File copied: ./README.2.md 

وقت الإطلاق صعب القياس. ولكن يمكننا التحقق من مقدار الذاكرة التي تأخذها عمليتنا ، على سبيل المثال ، مراقب النشاط: 852 كيلو بايت. ليس سيئا!

بعض الاستنتاجات


وهكذا ، اكتشفنا أنه بمساعدة Kotlin Native يمكننا الحصول على ملف قابل للتنفيذ واحد مع بصمة ذاكرة أقل من نفس Go. النصر ليس حقا

كيفية اختبار كل شيء؟ أولئك الذين عملوا مع Go أو Kotlin'om يعرفون أنه توجد في كلا اللغتين حلول جيدة لهذه المهمة الهامة. Kotlin Native لديها صفقة سيئة معها حتى الآن.

يبدو أنه في 2017 JetBrains حاول حل هذا . لكن بالنظر إلى أن الأمثلة الرسمية لكوتلين الأصلية لا تملك اختبارات ، على ما يبدو لم تنجح بعد.

مشكلة أخرى هي تطوير crossplatform. ربما لاحظ أولئك الذين عملوا مع C أكبر من مثلي أن مثالي سيعمل على OSX ، ولكن ليس على Windows ، لأنني أعتمد على العديد من الوظائف المتاحة فقط مع platform.darwin . آمل أن يكون في Kotlin Native في المستقبل مزيد من الأغلفة التي تسمح لها بالاستخلاص من النظام الأساسي ، على سبيل المثال ، عند العمل مع نظام الملفات.

يمكنك العثور على جميع أمثلة الكود هنا.

ورابط لمقالتي الأصلية ، إذا كنت تفضل القراءة باللغة الإنجليزية

Source: https://habr.com/ru/post/ar435220/


All Articles