Kotlin Native: Verfolgen Sie die Dateien

Wenn Sie ein Befehlszeilenprogramm schreiben, möchten Sie sich als letztes darauf verlassen, dass JVM, Ruby oder Python auf dem Computer installiert ist, auf dem es ausgeführt wird. Ich hätte auch gerne eine Binärdatei, die einfach zu starten ist. Und kümmern Sie sich nicht zu sehr um die Speicherverwaltung.

Aus den oben genannten Gründen habe ich in den letzten Jahren immer dann, wenn ich solche Dienstprogramme schreiben musste, Go verwendet.

Go hat eine relativ einfache Syntax, eine gute Standardbibliothek, es gibt eine Garbage Collection und am Ausgang erhalten wir eine Binärdatei. Es scheint, dass was noch benötigt wird?

Vor nicht allzu langer Zeit begann Kotlin auch, sich in Form von Kotlin Native auf einem ähnlichen Gebiet zu versuchen. Der Vorschlag klang vielversprechend - GC, eine einzige binäre, vertraute und bequeme Syntax. Aber ist alles so gut wie wir möchten?

Das Problem, das wir lösen müssen: Schreiben Sie einen einfachen Datei-Watcher auf Kotlin Native. Als Argumente sollte das Dienstprogramm den Dateipfad und die Häufigkeit des Scans abrufen. Wenn sich die Datei geändert hat, muss das Dienstprogramm eine Kopie davon im selben Ordner mit dem neuen Namen erstellen.

Mit anderen Worten, der Algorithmus sollte folgendermaßen aussehen:

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

Okay, was wir erreichen wollen, scheint geklärt zu sein. Zeit, Code zu schreiben.

Mittwoch


Das erste, was wir brauchen, ist eine IDE. Vim Liebhaber bitte keine Sorge.

Wir starten die vertraute IntelliJ IDEA und stellen fest, dass es in Kotlin Native überhaupt nicht vom Wort kommt. Sie müssen CLion verwenden .

Die Missgeschicke der Person, die C zuletzt im Jahr 2004 begegnet ist, sind noch nicht vorbei. Benötigen Sie eine Toolchain. Wenn Sie OSX verwenden, findet CLion höchstwahrscheinlich die entsprechende Toolchain. Wenn Sie sich jedoch für Windows entscheiden und nicht in C programmieren, müssen Sie am Tutorial basteln, um Cygwin zu installieren.

IDE installiert, sortiert die Toolchain. Kann ich bereits mit dem Schreiben von Code beginnen? Fast.
Da Kotlin Native noch etwas experimentell ist, ist das Plugin dafür in CLion nicht standardmäßig installiert. Bevor wir also die geschätzte Inschrift "New Kotlin / Native Application" sehen, muss sie manuell installiert werden .

Einige Einstellungen


Und so haben wir endlich ein leeres Kotlin Native-Projekt. Interessanterweise basiert es auf Gradle (und nicht auf Makefiles) und sogar auf der Kotlin Script-Version.

Schauen build.gradle.kts sich build.gradle.kts :

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

Das einzige Plugin, das wir verwenden werden, heißt Konan. Er wird unsere Binärdatei erstellen.

In konanArtifacts wir den Namen der ausführbaren Datei an. In diesem Beispiel erhalten wir file_watcher.kexe

Code


Es ist Zeit, den Code bereits anzuzeigen. Hier ist es übrigens:

 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 } } 

In der Regel verfügen Befehlszeilenprogramme auch über optionale Argumente und deren Standardwerte. Wir gehen jedoch beispielsweise davon aus, dass es immer zwei Argumente gibt: path und interval

Für diejenigen, die bereits mit Kotlin gearbeitet haben, mag es seltsam erscheinen, dass der path in eine eigene File Klasse eingeschlossen wird, ohne java.io.File . Die Erklärung dafür ist in ein oder zwei Minuten.

Wenn Sie mit der Funktion require () in Kotlin nicht vertraut sind, ist dies nur eine bequemere Möglichkeit, die Argumente zu überprüfen. Kotlin - alles dreht sich um Bequemlichkeit. Man könnte so schreiben:

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

Im Allgemeinen ist hier der übliche Kotlin-Code, nichts Interessantes. Aber von jetzt an wird es Spaß machen.

Versuchen wir, normalen Kotlin-Code zu schreiben, aber jedes Mal, wenn wir etwas aus Java verwenden müssen, sagen wir "Ups!". Bist du bereit

Kehren wir zu unserer while und lassen Sie jedes interval ein Symbol einprägen, zum Beispiel einen Punkt.

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

Thread ist eine Klasse aus Java. Wir können keine Java-Klassen in Kotlin Native verwenden. Nur Kotlins Klassen. Kein Java.

Übrigens, weil wir main java.io.File nicht verwendet java.io.File

Nun, was kann dann verwendet werden? Funktionen von C!

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

Willkommen in der Welt C.


Nachdem wir nun wissen, was uns erwartet, wollen wir in unserer File sehen, wie die Funktion exist exists() aussieht:

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

File ist eine einfache data class , die uns die Implementierung von toString() aus der Box gibt, die wir später verwenden werden.

"Unter der Haube" rufen wir die access() Funktion C auf, die -1 zurückgibt, wenn eine solche Datei nicht existiert.

Weiter unten in der Liste haben wir die Funktion modified() :

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

Die Funktion könnte durch Typinferenz etwas vereinfacht werden, aber hier habe ich beschlossen, dies nicht zu tun, damit klar ist, dass die Funktion beispielsweise nicht Boolean .

Diese Funktion enthält zwei interessante Details. Zuerst verwenden wir alloc() . Da wir C verwenden, müssen wir manchmal Strukturen zuweisen, und dies erfolgt in C manuell mit malloc ().

Diese Strukturen müssen auch manuell freigegeben werden. Die memScoped() -Funktion von Kotlin Native memScoped() , was für uns memScoped() wird.

Es bleibt uns opyAside() die gewichtigste Funktion zu betrachten: 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 } 

Hier verwenden wir die C-Funktion copyfile_state_alloc() , die die für copyfile() benötigte Struktur auswählt.

Aber wir müssen diese Struktur selbst freigeben - mit
copyfile_state_free(state)

Das Letzte, was noch zu zeigen ist, ist die Generierung von Namen. Es gibt nur einen kleinen Kotlin:

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

Dies ist ein ziemlich naiver Code, der viele Fälle ignoriert, aber für ein Beispiel ausreicht.

Starten Sie


Wie läuft das alles?

Eine Möglichkeit ist natürlich die Verwendung von CLion. Er wird alles für uns tun.

Verwenden wir stattdessen die Befehlszeile, um den Prozess besser zu verstehen. Ja, und einige CI führen unseren Code nicht über CLion aus.

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

Zunächst kompilieren wir unser Projekt mit Gradle. Wenn alles gut gegangen ist, wird die folgende Meldung angezeigt:

 BUILD SUCCESSFUL in 16s 

Sechzehn Sekunden ?! Ja, im Vergleich zu einigen Go oder sogar Kotlin für die JVM ist das Ergebnis enttäuschend. Und das ist immer noch ein winziges Projekt.

Sie sollten jetzt Punkte auf dem Bildschirm sehen. Wenn Sie den Inhalt der Datei ändern, wird eine Meldung angezeigt. So etwas wie dieses Bild:

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

Die Startzeit ist schwer zu messen. Wir können jedoch überprüfen, wie viel Speicher unser Prozess benötigt, indem wir beispielsweise Activity Monitor: 852 KB verwenden. Nicht schlecht!

Einige Schlussfolgerungen


Und so haben wir herausgefunden, dass wir mit Hilfe von Kotlin Native eine einzelne ausführbare Datei mit einem Speicherbedarf erhalten können, der geringer ist als der des gleichen Go. Sieg Nicht wirklich.

Wie teste ich alles? Diejenigen, die mit Go oder Kotlin'om gearbeitet haben, wissen, dass es in beiden Sprachen gute Lösungen für diese wichtige Aufgabe gibt. Kotlin Native hat bisher ein schlechtes Geschäft damit.

Es scheint, dass JetBrains im Jahr 2017 versucht hat, dies zu lösen . Aber wenn man bedenkt, dass selbst die offiziellen Beispiele der Kotlin Native keine Tests haben, anscheinend noch nicht zu erfolgreich.

Ein weiteres Problem ist die plattformübergreifende Entwicklung. Diejenigen, die mit C gearbeitet haben, das größer als meins ist, haben wahrscheinlich bereits bemerkt, dass mein Beispiel unter OSX funktioniert, aber nicht unter Windows, da ich auf mehrere Funktionen angewiesen bin, die nur mit platform.darwin verfügbar platform.darwin . Ich hoffe, dass Kotlin Native in Zukunft mehr Wrapper haben wird, die es ihm ermöglichen, von der Plattform zu abstrahieren, beispielsweise wenn er mit dem Dateisystem arbeitet.

Alle Codebeispiele finden Sie hier.

Und ein Link zu meinem Originalartikel , wenn Sie lieber auf Englisch lesen möchten

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


All Articles