Es gibt viele Möglichkeiten in Grunt, Dateien zu kopieren und das Kopieren von Dateien ist eine der häufigsten Operationen bei der Automatisierung. Deshalb sollte man sich frühzeitig mit einigen grundsätzlich verschiedenen Möglichkeiten vertraut machen. Die folgenden fünf Beispiele wurden so ausgewählt und gestaltet, dass sie ihren typischen Einsatzbereich andeuten und man sie für ihren praktischen Einsatz mit Copy & Paste übernehmen und leicht an die eigenen Bedürfnisse anpassen kann.
1.) Kopieren mit Grunt-Core-Funktionen und Javascript
Diese Methode eignet sich dann, wenn man Dateien während des Kopierens verändern will. Der einfachste denkbare Fall wäre z.B. das Hinzufügen eines Headers oder Copyrights im Kopf von Programm-Sourcen. Komplexere Anwendungsfälle sind dagegen Syntax-Überprüfungen, Compilierungen und so weiter. Hier ein Beispiel, in dem einer Datei während des Kopierens ein simples Copyright (c) Willi hinzugefügt wird.
'use strict'; module.exports = function(grunt) { grunt.initConfig({ pkg : grunt.file.readJSON('package.json'), copy_file: { "main": { src: "C:\\Work\\files\\file1.txt", dest : "C:\\Work\\tmp" } } }); grunt.registerTask('doCopyByProg', 'Kopiert eine Datei per Programm ...', function(){ console.log('Quelle: ' + grunt.config('copy_file.main.src')); console.log('Ziel: ' + grunt.config('copy_file.main.dest')); var src = grunt.config('copy_file.main.src'); var dest = grunt.config('copy_file.main.dest'); var srcFilename = src.substring(src.lastIndexOf("\\")+1); var destfile = dest + '\\' + srcFilename; var buff = ''; var options = { process : function(contents, src) { // Don't do that or similar: // var buff = grunt.file.read(src); // grunt.file.write(destfile, buff); // Do this instead: buff = '(c) Willi \r\n' + contents /*buff*/; return buff; } }; grunt.file.copy( grunt.config('copy_file.main.src'), destfile, options ); grunt.log.write('Dateien kopiert!').ok(); }); // Copy files by program grunt.registerTask('copyByProg', ['doCopyByProg']); }
Diese Methode benötigt keine zusätzlichen Grunt-Module. Es werden nur Funktionen der Grunt-Core-Lib grunt.file verwendet. Kernpunkt ist der Options-Parameter process, eine implementierbare Funktion, in der man den Inhalt der zu kopierenden Datei vor dem Abspeichern verändern kann.
2.) Kopieren mit dem Modul grunt-contrib-copy
Natürlich gibt es für eine so wichtige Aufgabe wie Kopieren ein (oder sogar mehrere) Module für Grunt. Im folgenden wird das Modul grunt-contrib-copy vorgestellt und zwar in einem für dieses Modul typischen Zusammenhang, dem Erstellen eines Build.
'use strict'; module.exports = function(grunt) { grunt.initConfig({ pkg : grunt.file.readJSON('package.json'), copy:{ build:{ cwd:'C:\\Work\\files', src:['**'], dest:'C:\\Work\\tmp', expand:true } }, clean:{ build:{ src:'C:\\Work\\tmp' }, options:{ force:true // don't block deletion of folders outside current working dir } } }); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-clean'); // Copy by modul grunt-contrib-copy (and grunt-contrib-clean) grunt.registerTask( 'build', 'Compiles all the assets and copies the files to the build directory.', ['clean', 'copy'] // [ 'clean:build', 'copy:build' ] ); }
Dieses Skript hier benötigt die Installation der Module grunt-contrib-copy und grunt-contrib-clean, wobei clean einen Zielordner vor dem Kopieren vollständig aufräumt.
3.) Kopieren mit Hilfe eines Shell-Kommandos, für Windows z.B. xcopy
Es ist nie eine schlechte Idee, die Möglichkeiten des jeweiligen Betriebssystems zu nutzen.
Hierfür stellt Grunt mehrere Module zur Verfügung, z.B. grunt-exec oder grunt-shell. Damit lassen sich dann die Kopier-Programme des jeweiligen Betriebssystems nutzen.
Im folgenden Skript wird das Windows-Kopierprogramm xcopy verwendet. Auf Linux gibt es analoge Programme.
'use strict'; module.exports = function(grunt) { grunt.initConfig({ pkg : grunt.file.readJSON('package.json'), shell : { "xcopy" : { command : [ 'xcopy "files\\file1.txt" "tmp" /Y /R /C /V', 'xcopy "files\\file2.txt" "tmp" /Y /R /C /V' ].join('&&'), options: { stderr: false, execOptions: { cwd: 'C:\\Work' } } }, } }); grunt.loadNpmTasks('grunt-shell'); // Copy by xcopy with Modul grunt-shell grunt.registerTask('xcopy', ['shell:xcopy']); }
Vor allem, wenn man schnell einige einzelne Dateien immer wieder irgendwohin kopieren muss, z.B. die jeweiligen settings.php's für Multi-Site-Drupal-Installationen beim Staging & Deployment, ist xcopy keine schlechte Wahl.
Weil auf diese Weise die Skripte übersichtlich bleiben, weil der Aufruf der Kommandos mit ihren Parametern sehr komprimiert erfolgen kann, nutze ich diese Möglichkeit sehr gerne.
4.) Inkrementelles Kopieren auf Windows mit Robocopy
Eine Methode, die einen absoluten Ehrenplatz verdient in dieser Liste, ist das Kopieren mit Robocopy, natürlich nur für Windows-Nutzer.
Sie ermöglicht das inkrementelle Kopieren und dies sehr schnell und effizient, wobei gleichzeitig ein Logfile ausgegeben werden kann, das so konfiguriert werden kann, dass es nur die Dateien enthält, die kopiert wurden. Allerdings muss dieses Logfile trotzdem etwas bearbeitet werden, bevor man es weiterverwendet.
Dies ist nützlich, wenn man z.B. nach einem Build, für das man Robocopy eingesetzt hat, die Daten noch in ein Staging- bzw. Produktionssystem per SSH, FTP oder ähnliches hochladen möchte und zwar nur die aktuell veränderten Dateien. Denn nur so lässt sich ein effizienter Staging & Deployment-Prozess aufbauen.
Ein Build dagegen, das z.B. eine komplette Drupal-Installation umfasst, benötigt auch gepackt für das Erstellen des Build, die Komprimierung und das Hochladen auf den Remote-Server locker mehr als eine Stunde und das ist meistens unakzeptabel.
Man kann Robocopy natürlich auch mit grunt-shell oder grunt-exec starten. Das habe ich natürlich auch zuerst versucht, bin dann aber auf das Problem gestoßen, dass Robocopy seltsame Exit-Codes verwendet, so dass zumindest grunt-shell Schwierigkeiten hat, eine fehlerfreie Ausführung zu erkennen.
Dann bin ich auf das Modul grunt-robocopy gestoßen, bei dem diese Probleme gelöst sind und ab da war ich von Robocopy überzeugt.
Hier ein Vorschlag für die Parameterisierung von Robocopy für inkrementelles Spiegeln von Dateien:
'use strict'; module.exports = function(grunt) { grunt.initConfig({ pkg : grunt.file.readJSON('package.json'), robocopy: { options: { files: ['*'], file: { excludeOlderFiles: true, // XO excludeFiles: [ // XF 'npm-debug.log', 'example.sites.php', '*.bat', '*.zip', '*.tar.gz', '*.tgz', 'Gruntfile.js', 'package.json', 'README.txt' ], excludeDirs: [ // XD '.git', 'whr_crud', 'whr_test', 'whr_cool', 'Module parken', 'node_modules', 'nbproject', 'symfony', 'symfonyblog', 'DrupalGapBlogCodeKiste', 'CordovaMapsSample', 'DrupalGapNBProjekt', 'mobile-application' ] }, copy: { // listOnly: true, // L // subdirs: true, // S // multiThreaded: true, // MT[:N] mirror: true // MIR }, logging: { excludeDirectoryNames: true, // NDL noJobHeader: true, // NJH noJobSummary: true, // NJS hideProgress: true, // NP output: { // LOG: file: 'mirror.log', overwrite: true, unicode: false } } }, mirror: { options: { source: 'C:\\Work\\files', destination: 'C:\\Work\\tmp' } } } }); grunt.loadNpmTasks('grunt-robocopy'); // Incremential copy by modul grunt-robocopy grunt.registerTask('rocopy', ['robocopy:mirror']); }
Die Parameter wurden sehr bewusst so gewählt für dieses Beispiel-Skript. Sie sind etwas gewöhnungsbedürftig und es gibt eine große Liste davon. Es ist also kein Fehler, das Skript zunächst einmal so zu verwenden wie es ist, bevor man Änderungen daran vornimmt. Natürlich sollte man unten source und destination und oben die Exclude-Files und -Dir's an die eigenen Anforderungen anpassen.
5.) Inkrementelles Live-Kopieren mit grunt-contrib-watch und grunt-sync
Als letztes stelle ich noch die übliche Methode für die Live-Erstellung eines Builds mit Hilfe des Moduls grunt-contrib-watch vor, wobei hierbei ebenfalls inkrementelles Kopieren möglich ist durch zusätzlichen Einsatz des Moduls grunt-sync.
'use strict'; module.exports = function(grunt) { grunt.initConfig({ pkg : grunt.file.readJSON('package.json'), sync: { copy: { files: [ { cwd: 'C:\\Work\\files', src: '**', dest: 'C:\\Work\\tmp' } //,{ cwd: 'src', src: 'res/**', dest: 'www' } ], verbose:true } }, watch:{ files:['**/*.*'], tasks:['sync:copy'], // tasks:['robocopy:mirror'], options: { //spawn: false, cwd: 'C:\\Work\\files' }, } }); grunt.loadNpmTasks('grunt-sync'); grunt.loadNpmTasks('grunt-contrib-watch'); // Watched copy grunt.registerTask('default', ['watch']); }
Für die Synchronisierung kann man unter Windows auch das Modul grunt-robocopy sehr gut verwenden. Dafür muss man natürlich die Skripte 4 und 5 kombinieren, was kein Problem darstellen dürfte.
Vielleicht komme ich dazu, die beiden Möglichkeiten für die Synchronisierung bezüglich ihrer Performance zu vergleichen. Ich werden dann berichten. Was im Moment für Robocopy spricht, ist die Möglichkeit der Ausgabe einer Liste der übertragenen Dateien in einem Logfile, was wie schon erwähnt für Staging & Deployment wichtig ist. Inwieweit man das grunt-sync-Modul dahingehend erweitern kann, habe ich noch nicht genau untersucht. Für viele Module stehen ja Hooks und Callback-Funktionen zur Verfügung, mit denen man häufig noch nicht vorhandene Funktionalität hinzufügen kann.
Man sieht, dass das Thema Kopieren mit Grunt ein ziemlich komplexes Thema ist.