sasswatch reloaded: Neue Version unseres Gentoo-Dienstes zur Sass-Kompilierung

Veröffentlicht als Einblick
von Joschi Kuphalam

Seit ich im Februar unseren kleinen Gentoo-Linux-Dienst sasswatch geschrieben und auf unseren Entwicklungsservern installiert habe, hat sich unsere Arbeitsweise in Sachen CSS völlig verändert – nicht nur für mich. Inzwischen haben wir sämtliche aktuellen Projekte flächendeckend auf Sass umgestellt, und freiwillig wird keiner von uns mehr darauf verzichten wollen. Unser Azubi Jeff meinte: »Keine Ahnung, wie ich jemals ohne Sass arbeiten konnte«, und selbst Andi gesteht, dass es sich zwischenzeitlich ziemlich faustkeilartig anfühlt, wenn er für private Projekte ohne den Sass-Compiler auskommen muss ...

Trotzdem hat sich nach ein paar Monaten Praxis der eine oder andere Nachteil dieser – zugegeben einfachen – ersten Lösung aufgetan:

  • Zwar lassen sich für den ursprünglichen sasswatch-Dienst beliebig viele Konfigurationsdateien für die zu überwachenden SASS-Dateien und -Verzeichnisse anlegen, letztendlich werden diese jedoch für die Überwachung zusammengefasst und von einem einzigen sass --watch-Prozess betreut.
    Dies hat in der Praxis zunächst nur für Verwirrung, mit wachsender Projektanzahl aber zu ernsthaften Problemen geführt: Zum einen wirft der sass --watch-Prozess offenkundig alle überwachten Dateien und Verzeichnisse in einen großen Topf. Enthalten zwei oder mehr Projekte Sass-Dateien mit denselben Namen, und schreibt man Dateipfade in @import-Anweisungen nicht super-explizit (z.B. pfad/zum/include statt ./pfad/zum/include), dann passiert es leicht, dass der Compiler einfach eine Datei aus einem völlig anderen Projekt einbindet. Ferner, baut man einen Fehler in eine der Sass-Dateien eines Projekt ein, stehen die Chancen gut, dass auch andere Projekte nicht mehr kompiliert werden können, weil sich der Sass-Compiler an der fehlerhaften Datei aufhängt.
    Kurzum: Es musste eine Lösung her, bei der alle Projekte isoliert behandelt werden, um sich nicht gegenseitig zu stören.
  • Sass bietet zur Kompilierung verschiedene »Stile« bzw. Kompressionsgrade für das resultierende CSS an. So scheint für die Übersetzung von Sass-Daten, die fertig für den Produktivbetrieb sind, beispielsweise der compressed-Stil besonders geeignet, bei dem das Ergebnis-CSS vollständig minifiziert wird (Entfernung aller überflüssigen Zeichen und Reduktion auf die kürzestmöglichen Schreibweisen). Für die Bearbeitungsphase, in der man hin und wieder ins erzeugte CSS sehen und etwas verstehen möchte, ist dagegen z.B. der expanded-Stil geeigneter. Mit der ersten sasswatch-Variante kann die Einstellung des Rendering-Stils nur global vorgenommen werden. Eine Unterscheidung zwischen einzelnen Projekten ist nicht möglich. Analoges gilt für die Einbindung zusätzlicher Sass-Bibliotheken (wie z.B. compass-animation) über das require-Feature.

sasswatch – die Zweite

Heute nun habe ich mir die Zeit genommen und unseren sasswatch-Dienst überarbeitet. Wie auch den Vorgänger möchte ich die Verbesserungen hier kurz vorstellen und den neuen Dienst zum Herunterladen zur Verfügung stellen.

Die wesentliche Neuerung liegt darin, dass nun GNU Parallel zum Einsatz kommt, um mehrere gleichzeitige und voneinander unabhängige sass --watch-Prozesse zu starten – genauer einen je Projekt. parallel steht unter Gento als Paket im Portage-Tree zur Verfügung und kann ganz einfach installiert werden, sollte dies noch nicht der Fall sein:

emerge sys-process/parallel

Das eigentliche Dienstskript /etc/init.d/sasswatch hat sich dahingehend geändert, dass die vorgefundenen Projektkonfigurationen nun zunächst in einzelne sass --watch-Prozesskommandos übertragen und schließlich an parallel übergeben werden. (Ein Nebeneffekt für Neugierige: Die ausgeführten sass-Kommandos werden bei Dienststart in die Temporärdatei /var/run/sasswatch.cmd geschrieben und können dort eingesehen werden.)

#!/sbin/runscript
# Copyright 2013 Joschi Kuphal
depend() {
        need localmount
        after bootmisc
        use logger
}
start() {
        ebegin "Starting sasswatch"
        function trim {
                echo $1 | sed 's/^ *//g' | sed 's/ *$//g';
        }
        function watch {
                if [[ "$1" =~ .*:.* ]]; then
                        sass="`echo \"$1\" | cut -d: -f1`";
                        sass=$(trim "$sass");
                        css="`echo \"$1\" | cut -d: -f2`";
                        css=$(trim "$css");
                        if [ "$sass" != "" -a "$css" != "" -a \( \( -d "$sass" -a -d "$css" \) -o \( -f "$sass" -a \( -f "$css" -o ! -e "$css" \) \) \) ]; then
                                echo " $sass:$css";
                        fi;
                fi;
        }
        sasscmd="`which sass`";
        sassopts="--unix-newline --style ${SASSWATCH_STYLE:-expanded}";
        sassdefaultstyle="${SASSWATCH_STYLE:-expanded}";
        sassdefaultcompass="${SASSWATCH_COMPASS:-1}";
        sassdefaultrequire="${SASSWATCH_REQUIRE}";
        rm /var/run/sasswatch.cmd;
        projects=0;
        # Determine all files and directories that have to be observed
        for watchconfig in `find /etc/sasswatch.d -maxdepth 1 -type f`; do
                style="$sassdefaultstyle";
                compass="$sassdefaultcompass";
                require="$sassdefaultrequire";
                watch="";
                source "$watchconfig";
                cmd="$sasscmd --unix-newline --style ${style}";
                # Determine if the compass framework shoud be loaded
                if [ "${compass}" = "1" ]; then
                        cmd="${cmd} --compass";
                fi;
                # Determine if additional libraries should be required
                if [ "$require" != "" ]; then
                        for req in $require; do
                                cmd="${cmd} --require ${req}";
                        done;
                fi;
                watches="";
                for w in $watch; do
                        watches="${watches} $w";
                done;
                if [ "$watches" != "" ]; then
                        echo "$cmd --watch $watches" >> /var/run/sasswatch.cmd;
                        projects=$((projects + 1));
                fi;
        done;
        start-stop-daemon --start --background --make-pidfile \
                --pidfile /var/run/sasswatch.pid --exec parallel \
                --stdout /var/log/sasswatch.log -- \
                -j $projects -a /var/run/sasswatch.cmd
        eend $?
}
stop() {
        ebegin "Stopping sasswatch"
        pid="`cat /var/run/sasswatch.pid`";
        sh -c "kill -TERM -${pid}";
        start-stop-daemon --stop --quiet --pidfile /var/run/sasswatch.pid
        eend $?
}

Die Basiskonfiguration des Dienstes /etc/conf.d/sasswatch hat sich inhaltlich nicht geändert, außer dass hier künftig die Standardwerte definiert werden, die nur noch dann greifen, wenn in den einzelnen Projektkonfigurationen keine anderweitigen Einstellungen vorgenommen werden:

# The commented variables in this file are the defaults that are used
# in the init-script.  You don't need to uncomment them except to
# customize them to different values.
# Default options for sasswatch
# SASSWATCH_COMPASS="1"
# SASSWATCH_STYLE="expanded"
# SASSWATCH_REQUIRE="animation"

Geändert hat sich vor allem das Format der Projekt-Konfigurationsdateien, in denen künftig nicht nur die zu überwachenden Dateien und Verzeichnisse definiert werden, sondern auch der jeweilige Ausgabestil und die einzubindenden Zusatzbibliotheken für diese einzelne Projekt. Nach wie vor müssen Projektkonfigurationen in das Verzeichnis /etc/sasswatch.d gelegt werden, um vom sasswatch-Dienst aufgegriffen zu werden. Eine Projektkonfiguration könnte z.B. folgenden Inhalt haben:

compass="1";
require="animation";
style="compact";
watch="/path/to/sass/dir1:/path/to/css/dir1
/path/to/sass/dir1:/path/to/css/dir1";

Die Konfigurationsdatei ist nun so aufgebaut, dass sie per source in Shell-Skripte eingebunden werden kann und folgende Variablen definiert:

  • compass: Steuert, ob die compass-Bibliothek eingebunden wird ("1") oder nicht ("0"). Die Angabe dieser Variable ist optional, es greift im Zweifelsfall die globale Voreinstellung.
  • require: Gibt die Zusatzbibliotheken an, die geladen werden sollen. Die Namen der Bibliotheken sind durch Leerzeichen zu trennen. Die Angabe dieser Variable ist optional, es greift im Zweifelsfall die globale Voreinstellung.
  • style: Gibt den Stil / Kompressionsgrad für das resultierende CSS an. Gültige Werte sind "nested", "expanded", "compact" und "compressed". Weitere Informationen finden sich in der Sass-Referenz. Die Angabe dieser Variable ist optional, es greift im Zweifelsfall die globale Voreinstellung.
  • watch: Gibt die zu überwachenden Dateien und Verzeichnisse bzw. die korrespondierenden Ausgabeorte im Format /ueberwachter/pfad/mit/sass/dateien:/zielpfad/fuer/css/dateien an. Innerhalb der Variable können mehrere solche Überwachungskonfigurationen für das Projekt definiert werden, diese sind durch Leerzeichen oder Zeilenumbrüche voneinander zu trennen. Dies ist die einzige Pflichtangabe in einer Projektkonfiguration, ohne sinnvolle Überwachungskonfiguration wird kein sass --watch-Prozess für das Projekt gestartet.

Weitere nützliche Informationen, z.B. zur Installation und zum Betrieb des sasswatch-Dienstes unter Gentoo, habe ich bereits ausführlich im Februar-Artikel zur Vorgängerversion, die ich deshalb hier nicht wiederhole. Mit ein wenig Systemkenntnis ist das Prinzip bestimmt auch auf andere Linux-Distributionen übertragbar.

Das neue sasswatch-Initscript samt Konfigurationsdatei kann zusammen mit einer beispielhaften Projektkonfiguration

hier aus dem GitHub-Repository heruntergeladen

werden. Bitte entpacken Sie das Archiv direkt auf der Rootebene / Ihres Gentoo-Servers. Auf Anfrage kann ich das Ganze auch gerne als Gentoo-ebuild zur Verfügung stellen. Über Feedback und Anregungen würde ich mich natürlich wieder sehr freuen!