SCons statt make für AVR-GCC

Wer kennt das nicht? Mal eben schnell was compilieren, doch ohweh, wie ging das noch mit make? So ging es mir in den vergangenen Tagen. Wollte nur ein kleines Projekt mit 2-3 Dateien compilieren und habe in dem Zug auch gleich versucht make besser zu verstehen.

Erfolglos.

Dann fiel mein Blick auf SCons und ich erinnerte mich daran, dass ich davon schon einmal gehört habe. Ich habe die Dokumentation von SCons überflogen, habe eine SConstruct Datei gesehen (das Equivalent zu einem makefile) und habe verstanden worum es geht. Heute Morgen habe ich die Datei etwas modifiziert, den Befehl scons in der Konsole eingegeben, fertig. Programm wurde compiliert.

Dabei hat SCons noch wesentlich mehr Möglichkeiten als make, allein schon deshalb weil eine SConstruct Datei nichts anderes ist als ein Python Script. Das heißt das man alle Python Befehle verwenden und andere Module importieren kann. Auch das Handling von Abhängigkeiten ist mit SCons (meiner Meinung nach) wesentlich einfacher.

Die größten Vorteile gegenüber make sind die Möglichkeiten, die sich durch Python ergeben, das einfachere Handling von Abhängigkeiten und die Environments. Es gibt aber noch viel mehr Funktionen wie Repositories oder die Möglichkeit, automatisch verschiedene Paketformate zu erstellen

Ich hoffe ich kann die Windows Nutzer jetzt ein wenig neidisch machen wenn ich erkläre wie man SCons unter Linux installiert ;-). Unter Ubuntu gibt man in der Konsole einfach diesen Befehl ein:

sudo apt-get install scons

Fertig. Mit Winavr unter Windows lässt sich das sicher auch verwenden, nur die Installation wird wohl nicht ganz so einfach.

Das SConstrukt

Das SConstruct ist vergleichbar mit dem Makefile von make, nur das es sich dabei um ein normales Python Script handelt. Ich habe das erste SConstruct von Calle's Pinar Projekt etwas angepasst. In dem Projektverzeichnis gibt es das Unterverzeichnis /lib in dem weitere *.c und *.h Dateien liegen.

"""     SConstruct
#
#       Copyright 2008 Markus Burrer & CJT, cjt@users.sourceforge.net
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
"""
avr = Environment()
 
Target="main"
mcu="atmega128"
F_CPU=16e6
 
# Add all additional source files to compile
src=""" obj/utils.c
        obj/usart.c
        obj/usart1.c"""
 
# Add Source Path here
cpppath=""" .
            ../../EP-gcc-lib"""
 
# Optimization level, can be [0, 1, 2, 3, s].
# 0 = turn off optimization. s = optimize for size.
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
opt = "s"
 
# Add Additional Compiler Flags
flags=""" """
 
# Add Variant Dirs
avr.VariantDir('obj', '../../EP-gcc-lib')
 
 
# Set Environment Parameters
avr['CC'] = "avr-gcc -mmcu=%s" % mcu
avr.Append(CPPPATH = Split(cpppath))
avr.Append(CCFLAGS = "-O%s" % opt)
avr.Append(CCFLAGS = "-Wall" )
avr.Append(CCFLAGS = "-DF_CPU=%i" % F_CPU)
avr.Append(CCFLAGS = Split(flags))
 
# Execute AVR-GCC
avr.Program(Target+".elf", Split(Target+".c " + src))
 
avr.Command(Target+".hex", Target+".elf", "avr-objcopy -j .text -j .data -O ihex $SOURCE $TARGET")
# Show memory usage
avr.Command(None, Target+".elf", "avr-size $SOURCE")

Man speichert die Datei unter dem Namen SConstruct wie ein makefile in den Ordner mit den Source Dateien. Die Compilierung startet man einfach mit dem Befehl

scons

Möchte man die Objektdateien löschen (make clean) gibt man einfach

scons -c

ein.

Kurzreferenz

Program()

Mit dem Befehl Program() weist man Scons an, die angegebene Datei zu compilieren und daraus eine ausführbare Datei zu erstellen.

Program('hello.c')

Um der erzeugten Datei einen anderen Namen zu geben übergibt man Program() als ersten Parameter den gewünschten Namen.

Program('hello.elf', 'hello.c')

Dies erzeugt die Datei hello.elf

Um mehrere Source Files zu compilieren muss man eine Python Liste mit den gewünschten Dateien übergeben. Am einfachsten schließt man die Dateinamen in eckige Klammern ein.

Program('hello.elf', ['hello.c', 'usart.c', 'twi.c'])

Diese Anweisung compiliert die Dateien hello.c, usart.c und twi.c und erzeugt daraus die Datei hello.elf.

Scons akzeptiert auch Schlüsselworte, um die Sourcen und die Ausgabedatei zu definieren. Das Schlüsselwort für die Ausgabedatei ist target, die Sourcen werden als source bezeichnet

Program(target='hello.elf', source='hello.c')

Mit der Verwendung der Schlüsselworte darf auch die Reihenfolge geändert werden

Program(source='hello.c', target='hello.elf')

Object()

Der Befehl Object() erstellt aus der angegebenen Datei eine Object Datei

Object('hello.c')

Library()

Mit dem Befehl Library() kann man vorcompilierte Librarys erstellen. Beim avr-gcc ist dies jedoch nur bedingt sinnvoll, da man dies nur für generische Funktionen verwenden kann, die weder Taktabhängig noch abhängig von bestimmten Special Function Registern ist. Die avr-libc, die beim avr-gcc mitgeliefert wird, sind zum Beispiel die libc.a oder die libprintf_min.a als vorcompilierte Librarys enthalten.

Library('target', ['src1.c', 'scr2.c', scr3.c']

Um die erstellte Library mit dem Programm zu verlinken muss man die Library(s) und den Pfad zu den Librarys angeben

Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Library('bar', ['b1.c', 'b2.c', 'b3.c'])
Program('hello,elf', 'hello.c', LIBS=['foo', 'bar'], LIBPATH='.')

Standardmäßig sucht der Linker nur in den vom System definierten Pfaden. Im vorherigen Beispiel bedeutet LIBPATH='.', das im aktuellen Verzeichnis gesucht wird. Möchte man eigene Verzeichnisse verwenden muss man diese als Liste in LIBPATH angeben.

Program('prog.c', LIBS = 'm', LIBPATH = ['/usr/lib', '/usr/local/lib'])

Java()

Scons kann auch Java Compilieren. Das sei hier aber nur der Vollständigkeit halber erwähnt.

Java('classes', 'src')

Glob()

Mit dem Befehl Glob() werden alle Dateien compiliert, die zu dem angegebenen Pattern passen. Hierzu kann man die Standard Shell Pattern Zeichen verwenden, z.B. *, ?, [abc] oder [!abc]. Diese Anweisung compiliert alle Dateien mit der Endung .c

Program('hello.elf', Glob('*.c')

Split()

Um mehrere Dateien zu compilieren muss man eine Liste übergeben. Mit dem Befehl Split() kann man die Schreibweise vereinfachen. Man muss nur einen String übergeben, in dem alle Dateinamen durch ein Leerzeichen getrennt aufgelistet sind.

Program('hello.elf', Split('hello.c usart.c twi.c')

Die Dateiliste lässt sich auch in einer Variablen ablegen

source_files=Split('hello.c usart.c twi.c')
Program('hello.elf', source_files)

Um die Übersicht bei einer größeren Anzahl an Source Files zu behalten kann man den String mit den Dateien auf mehrere Zeilen aufteilen. Hierzu muss der String in dreifache Anführungszeichen eingeschlossen werden.

source_files=Split("""hello.c 
                      usart.c
                      twi.c""")
Program('hello.elf', source_files)

Node Objects

Die Scons Builder geben sogenannte Node Objects zurück. Diese können für weitere Builder verwendet werden.

Object('main.c')
Object('usart.c')
Program(['main.o', 'usart.o'])

Dieses Beispiel compiliert die beiden Dateien main.c und usart.c zunächst in Object Dateien. Anschließend werden sie von dem Befehl Program() verlinkt. Sollte sich an den Dateinamen etwas ändern muss man die Änderung überall berücksichtigen.

main=Object('main.c')
usart=Object('usart.c')
Program(main+usart)

In diesem Beispiel haben Änderungen an den Namen der Source Files keinen Einfluss auf Program(). Die Variablen main und usart übergeben automatisch die richtigen Angaben.

Command()

Der Befehl Command() dient zur Bearbeitung von Dateien und Verzeichnissen oder kann externe Befehle ausführen. Zur Bearbeitung von Dateien und Verzeichnissen gibt es Unterbefehle. Die grundsätzliche Struktur von Command() sieht so aus:

Command("file.out", "file.in", Subcommand("$TARGET", "$SOURCE"))

Command() hat drei Parameter, das Ziel (Target), die Quelle (Source) und den Unterbefehl. Ziel und Quelle werden in den Variablen $TARGET und $SOURCE an den Unterbefehl übergeben.

Copy()

Mit Copy() lassen sich Dateien und Verzeichnisse kopieren. Die Syntax sieht so aus.

Command('main.bak', 'main.c', Copy('$TARGET', '$SOURCE'))

Dieser Befehl erstellt eine Kopie der Datei main.c mit dem Namen main.bak

Besonders nützlich wird der Command() Befehl, wenn man mehrere Anweisungen ausführen möchte.

Command("file.out", "file.in",
        [
          Copy("tempfile", "$SOURCE"),
          "modify tempfile",
          Copy("$TARGET", "tempfile"),
        ])

Diese Sequenz kopiert file.in in die Datei tempfile. Anschließend wird der (fiktive) Kommandozeilenbefehl modify aufgerufen, der tempfile bearbeitet. Anschließend wird die modifizierte Datei tempfile in die Datei file.out kopiert.

Delete()

Mit Delete() lassen sich Dateien oder Verzeichnisse löschen. Man kann das obrige Beispiel zb so ergänzen.

Command("file.out", "file.in",
        [
          Delete("tempfile"),
          Copy("tempfile", "$SOURCE"),
          "modify tempfile",
          Copy("$TARGET", "tempfile"),
        ])

Selbstverständlich kann man die Variablen auch für Delete() verwenden.

Command("file.out", "file.in",
        [
          Delete("$TARGET"),
          Copy("$TARGET", "$SOURCE")
        ])

Weitere Befehle

Es gibt noch weitere Befehle für Command(), auf die hier nicht nähere eingegangen wird, da sie sich ähnlich verhalten. Die Befehle sind Move(), Touch(), Mkdir() und Chmod().

Externe Befehle

Mit Command() kann man auch externe Befehle aufrufen. Der Befehl wird mit seinen Parametern als dritte Option als String an Command() übergeben. Dieser String kann die Variablen $SOURCE und $TARGET enthalten.

Command("main.hex", "main.elf", "avr-objcopy -j .text -j .data -O ihex $SOURCE $TARGET")

VariantDir()

Der Befehl VariantDir() sorgt dafür, das ein Source File in einem anderen Verzeichnis compiliert wird. Normalerweise werden zum Beispiel Object Files im gleichen Verzeichnis abgelegt wie das entsprechende Source File.

VariantDir('build', 'src')
env = Environment()
env.Program('build/hello.c')

Die Datei hello.c befindet sich im Unterverzeichnis ./src. Von dieser Datei wird eine Kopie im Unterverzeichnis ./build erstellt und dort compiliert. Das Opject File wird ebenfalls in ./build abgelegt. Soll von dem Source File keine Kopie erstellt werden, benötigt man den Parameter duplicate.

VariantDir('build', 'src', duplicate=0)
env = Environment()
env.Program('build/hello.c')

In diesem Fall wird keine Kopie von der Datei hello.c erstellt. Die compilierte Datei wird in ./build abgelegt.

Environment()

Die Funktion Environment() ist meiner Meinung nach eine der mächtigsten Funktionen in Scons. Mit ihrer Hilfe kann man verschiedene Umgebungen anlegen. Ein klassisches Beispiel sind die Einstellungen für debug und release. Für jede Umgebung lassen sich zum Beispiel unterschiedliche Compiler Flags angeben. Ein einfaches Beispiel dafür ist das SConstruct am Anfang dieses Artikels.

Diskussion

Geben Sie Ihren Kommentar ein (Wiki-Syntax ist zugelassen):
Wenn Sie die Buchstaben auf dem Bild nicht lesen können, laden Sie diese .wav Datei herunter, um sie vorgelesen zu bekommen.
/www/htdocs/w00645de/dokuwiki/data/pages/mikrocontroller/avr/scons_avr.txt · Zuletzt geändert: 2009/05/02 21:09 von burli
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0