Pfade in make und clang.

rubricanis

Homo ludens
Ich habe schon einige Jahre nichts mehr mit C/C++ gemacht und möchte jetzt ein angefangenes, altes Projekt fertigstellen, - mit clang und make. Allerdings stoße ich da bei beiden auf Widerstand was das file layout angeht, ich bekomme die entsprechenden Pfade nicht realisiert.

Mein file layout sieht in etwa so aus:
Code:
Makefile
src/
    app/
         main.cpp
         Shell.cpp
         .....
    xyz/...
    lua/...
inc/
    app/
        Shell.hpp
         ....
    xyz/
        Xyz.hpp
        ...
    lua/...
lib/ ...
./build
Xyz und lua sollen zu statischen und dynamische Biblotheken compiliert werden. Die Pfade zum Quellcode(.cpp) kann ich in make mit ".PATH" bekannt machen, leider funktioniert das nicht mit ".hpp", nur mit ".h", und da meckert clang. Na ja, nicht tragisch, da muss ich eben im Makefile die Pfade bei den Abhängigkeiten direkt angeben.

Was ich nicht hinbekomme ist clang++ die Pfade zu den .hpp Dateien mitzuteilen. Wenn ich man clang richtig verstehe ist das mit -I bzw. -F möglich, aber das bekomme ich aus irgenwelchen Gründen nicht hin. Wie werden da die Pfade angegeben? Ich hatt -Iinc/xyz bzw. -Finc/ versucht aber das funtioniert nicht, auch nicht mit ./ davor.

Ein weiteres Problem ist das make alle .o Dateien auf den Toplevel schmeißt. Angeblich geht das mit .OBJDIR aber damit komme ich überhaupt nicht klar. Am liebsten wäre es mir wenn ich die in .build/xyz etc bekäme.

Und noch etwas: Im alten Projekt (ich glaube das hatte ich entweder mit Eclipse oder premake + Mingw gemacht) hatte ich im Sourcecode die zu includierenden Dateine folgendermaßen angegeben, z.B. in Shell.cpp
Code:
#include <xyz/Xyz.hpp>
Natürlich ist das mit relativen Pfaden "../../inc/xyz/Xyz.hpp" möglich, aber das ist ja wohl nicht das Gelbe vom Ei.

Meine Güte, was für ein HeioPei! Kann mir jemand über diese Klippe hinweghelfen. Mit .o Dateien im jeweiligen Sourcecode Ordner kann ich leben, aber das andere ist eher "must have".
 
Und noch etwas: Wenn ich im Makefile folgendes für die Flags angebe, meckert clang dass -02 (Optimierung) nicht benutzt wird.
Code:
CXX = clang++
CXXFLAGS = -std=c++11 -O2
Verstehe einer die Welt ...:rolleyes:
 
Die beste Strategie ist meisten, projektinterne Pfade hartzucoden. Nur für systemweite Include-Pfade setzt man -I. Also zum Beispiel:
Code:
// Die Datei liegt in xyz/xyz.h, die .c Datei in abc/abc.c
#include "../xyz/xyz.h"
Und bitte keine Systemvariablen wie CXX in der Makefile hartcoden. Das ist eine ganz dumme Idee, weil es schon bei kleinen Abweichungen von deiner Plattform Ärger machen wird und den örtlichen Frickler massiv nerven.
 
Darf man Fragen, warum du src und inc in 2 Top-Level Verzeichnisse trennst? Gerade bei C++ ist doch für gewöhnlich recht viel Code im Header enthalten. Bei Templates nicht mal großartig anders möglich. Was spricht gegen:
Code:
src
  app/
    inc/
      main.hpp
    main.cpp
...

Somit brauchst du auch nicht großartig mit "../../../" includieren, sondern einfach immer "inc/file.hpp", bzw. "xyz/inc/Shell.hpp"
 
Die beste Strategie ist meisten, projektinterne Pfade hartzucoden. Nur für systemweite Include-Pfade setzt man -I.
Das erscheint mr in einer wiederverwendbaren Biblothek, sei es von mir oder irgend jemanden anderes, nicht sinnvoll. Denke mal an <boost/xxx> etc.
Und bitte keine Systemvariablen wie CXX in der Makefile hartcoden. Das ist eine ganz dumme Idee, weil es schon bei kleinen Abweichungen von deiner Plattform Ärger machen wird und den örtlichen Frickler massiv nerven.
Hmmm, muss ich drüber nachdenken. CXX wird ja durch Umgebungsvariable oder Cmd-Line Options überschrieben. Ist aber auch kein Problem einfach CX oder was auch immer zu nehmen.
Darf man Fragen, warum du src und inc in 2 Top-Level Verzeichnisse trennst? Gerade bei C++ ist doch für gewöhnlich recht viel Code im Header enthalten. Bei Templates nicht mal großartig anders möglich.
Na ja, es geht ja gerade darum die Header-Dateien unabhängig vom Source-Code zu machen damit sie wiederverwendbar sind. So kann man die kompilierten Libs und die Include Files unabhängig vonenander verwenden.
 
rubricanis schrieb:
Das erscheint mr in einer wiederverwendbaren Biblothek, sei es von mir oder irgend jemanden anderes, nicht sinnvoll. Denke mal an <boost/xxx> etc.
Achso, die Bibliothek war mir irgendwie entgangen. Was dir passiert, nennt man die "Include Hell". Es gibt verschiedene Wege um zu verhindern, dass man mit seinen Header-Dateien in Probleme gerät. Klassische Probleme sind zum Beispiel zyklische Abhängigkeiten zwischen Header-Dateien, sich widersprechende Anforderungen an die Reihenfolge ihres Einbindens und so weiter. Generell versuche ich:
- In einem Header so wenig andere Header wie möglich einzubinden. Stattdessen kommen die Includes in die Source-Dateien. Was in C gut umzusetzen ist, geht in C++ aufgrund der codelastigen Header aber nur bis zu einem gewissen Punkt. Include-Guards sind generell Pflicht. Insgesamt muss man bzw. sollte man Header so designen, dass es möglichst wenig Abhängigkeiten zwischen den Headern gibt.
- Von Beginn an eine möglicherweise über 50 Jahre dauernde Fortentwicklung des Projektes im Blick zu haben. Das bedeutet, dass es öffentliche Header gibt, in welchem die öffentliche API des Projekts definiert ist. Dazu kommen private Header, welche all den Kram enthalten, der lediglich intern genutzt werden soll. Auch sollte man sich schon mit dem ersten Release Gedanken über Dinge wie Symbol Versioning machen. Das macht sowohl dir, als auch deinen Nutzern auf lange Sicht deutlich weniger Ärger, als einfach drauf los zu entwickeln.

Wie dem auch sei. Alles an Includes was projektintern ist, würde ich immer hartcoden. In Headern, welche später öffentlich sein sollen, geht das natürlich nicht und man muss mit -I (ein großes i) den Pfad für systemweite Includes anpassen. Idealerweise strukturiert man die Includes dabei so, dass man mit möglichst wenigen -I Statements auskommt. In -I kann man absolute Pfade reinwerden, oder relative Pfade zum Arbeitsverzeichnis des Compilers. Das Arbeitsverzeichnis ist meist (aber nicht immer!) das Verzeichnis, in dem die Makefile liegt. Für dein Beispiel würdest bzw. könntest du die Header in "include/projektname" legen und ein "-Iinclude/" setzen. Einbinden würdest du dann:
Code:
#include <projektname/abcde.h>
Sobald man mehr als einen Header hat, sollte man ein Unterverzeichnis nehmen. Schaue dir mal an, was /usr/include und /usr/local/include für Müllberge sind, weil zu viele Projekte meinen ihre 25 Header einfach oben reinwerfen zu müssen. Unterverzeichnisse bringen Ordnung. :)
 
Nun zur Makefile. Praktisch jedes der etwa 20 "make" die es dort draußen gibt, hat ein systemweites Make-Script, was einige grundlegende Variablen und weitere Dinge setzt. Man kann sich also darauf verlassen, dass einige Variablen bereits gesetzt sind und kann sie als gegeben übernehmen. Sind sie nicht gesetzt, möchte der arme Anwender bitte seine total kaputte Plattform reparieren. Die Compiler - also CC und CXX - gehören immer und definitiv zu diesen grundlegenden Variablen. Ein Beispiel:
Code:
CXX = clang++
Hier setzt du CXX explizit auf clang++ und überschreibst, was die Plattform vorgibt. Klingt erst einmal nicht schlimm. Nun möchte ich deinen Code aber auf einem FreeBSD 8.4 bauen und bekomme
Code:
clang++: Befehl nicht gefunden.
Das ist dumm. Nun kann ich entweder das Makefile anpassen oder ich kann mir einen clang++ besorgen. Beides ist eher unschön. Hättest du zu FreeBSD 8 Zeiten g++ hartgecodet, hätten wir nun übrigens das gleiche Problem. Es gibt keinen g++ mehr. Besonders interessant wird es, wenn man Plattform wechselt und zum Beispiel ein Solaris hat, wo unter Umständen ganz andere Compiler zum Einsatz kommen. Daher überschreibe die Compiler generell nicht. Die Plattform gibt dir den richtigen und wenn nicht kann der Anwender es selbst per "make CXX=clang++" überschreiben.

Bei weiteren von der Plattform gestellten Variablen muss man schauen. CXXFLAGS gehören beispielsweise zu denen, die man durchaus überschreiben kann. Mann kann mit einer Zuweisung per "?=" Variablen zum Beispiel nur dann setzen, wenn sie nicht bereits gesetzt sind und somit dem Nutzer das Überschreiben möglich machen. Sehr oft ist es auch sinnvoll, die Variablen per ":=" zu definieren. Dann sind sie einmal definiert und werden bei allen Vorkommen stumpf durch den Inhalt ersetzt. Das gibt die Sicherheit, dass später nicht irgendwelche ungewollte Magie dazwischenfunkt und die Variable je nach Situation anders aufgelöst wird. Das es leicht schneller ist, interessiert heute aber eher keiner mehr.

Überhaupt ist es sinnvoll, sich zu überlegen welches Make man nutzen möchte. FreeBSDs bmake bzw. fmake ist beispielsweise durch ein sehr umfassendes Framework systemweiter Funktionalität sehr komfortabel, aber kaum portabel. gmake läuft hingegen auf allem, was einen Prozessor hat. Wenn man die Makefiles selbst schreiben möchte (wozu ich raten würde) und der Code auf einer Menge unterschiedlicher Systeme eingesetzt werden soll, ist gmake eigentlich immer die richtige Wahl.

Zu konkreten Problemen kann ich so allerdings nichts sagen. Dazu müsste man das Makefile sehen und schauen, wie die problematischen Variablen verarbeitet werden.
 
Yamagi, vielen Dank für deine ausführlichen Antworten! Das Prinzip der Aufteilung der Header Dateien habe ich genau so verstanden wie du das beschreibst. Im Prinzip geht es um die Biblothek xyz, app ist nur die aktuelle Laufzeitumgebung mit main.cpp etc. Lua ist eine etwas angepaßte Biblothek der Skriptsprache Lua die eingebunden wird. Beide Biblotheken möchte ich als statische und dynamische Biblothek haben.
Überhaupt ist es sinnvoll, sich zu überlegen welches Make man nutzen möchte. FreeBSDs bmake bzw. fmake ist beispielsweise durch ein sehr umfassendes Framework systemweiter Funktionalität sehr komfortabel, aber kaum portabel. gmake läuft hingegen auf allem, was einen Prozessor hat. Wenn man die Makefiles selbst schreiben möchte (wozu ich raten würde) und der Code auf einer Menge unterschiedlicher Systeme eingesetzt werden soll, ist gmake eigentlich immer die richtige Wahl.
Ich bin irrtümlicherweise davon ausgegangen das FBSD make weitgehend kompatibel zu gmake ist. Im manual ist übrigens nichts von fmake/bmake zu lesen, sondern ich hatte den Eindruck dass das pmake ist.

Aber egal, ich steige auf gmake um! Da muss ich mich aber auch erst noch einlesen da ich direkt noch nichts damit gemacht hatte.
Zu konkreten Problemen kann ich so allerdings nichts sagen. Dazu müsste man das Makefile sehen und schauen, wie die problematischen Variablen verarbeitet werden.
Das alte Makefile habe ich nicht mehr - und der Quellcode muss auch geändert werden - und bislang habe ich nur ein minimales Makefile gemacht um FreeBSD make auszuprobieren, - es ist also noch nichts verhackt!

Ich möchte das mit clang machen, schlicht um das kennen zu lernen und weil clang gegenüber gcc einige Vorzüge hat. Am besten wäre es allerdings wenn ich beide Compiler "conditional" verwenden könnte. Ich hatte GCC installiert und ein Makefile mit Premake4 generiert aber das hat nicht funktioniert da das nach g++ gesucht hat und GCC ist auf FBSD wohl anders installiert ist als unter Linux.

Bei Lua besteht ein ähnliches Problem das bei FBSD wohl die Linux Emulation verwendet, was ich gerne vermeiden will. Aber das sollte leicht anpassbar sein und ist kein vorrangiges Problem.

Was die Platform betrifft bin ich nur an *NIX interessiert da es sich um ein Server Projekt handelt. In Lua ist das so gelöst dass man da make <platform> aufruft was ich für eine gute Lösung halte, in diesem Falle also "make freebsd" was dann zum entsprechenden Compiler verzweigt. Mehr brauche ich hier zunächst nicht zu tun.

Später mehr, es gibt leider auch noch anderes zu tun .... :rolleyes:
 
Yamag & Alle, ich habe mir das noch einmal angesehen und sehe da bei der (selbst) gestellten Aufgabe einige Probleme die mit der Portabilität für unterschiedliche *NIX platformen und Compiler zusammen hängen. Hier mal mein erstes, minimales Makefile:
Code:
### variables ###

# application name
NAME ?= "bin/start"

### tools ###
MKDIR := mkdir -pv
RM := rm -rf

### targets ###
all: shell

shell: main.o Shell.o

info:
   @echo $(CXX)
   @echo $(CXXFLAGS)
   
### dependencies ###
   
main.o Shell.o : ./inc/xyz/Shell.hpp
main.cpp und Shell.cpp sind erst einmal nur minimale files die sich über std::cout melden.

Beim Aufruf von gmake info bekomme ich für CXX g++, gcc erreiche ich aber unter gcc49. Und bei gcc müssen im unterschied zu g++ die Sprache und die stdlib mit angegeben werden. Und bei clang wiederum eine andere clib. Ich denke daher dass es notwendig ist verschiedene Variable vorzudefinieren die dann ggf. vom User editiert oder eben überschieben werden können, - also mit ?=.

Dann stellt sich die Frage ob man die Platform-Spezifischen Dinge in ein extra Makefile, also z.B. fbsd10.mk auslagert was vielleicht sauberer ist oder das alles in einem Makefile macht was vielleicht allzu unübersichtlich wird.

Wie auch immer: ich muss mich jetzt erst einmal mit gmake und den Compilern näher beschäftigen und dann weitersehen. Für Vorschläge und Hinweise bin ich natürlich in hohem Maße dankbar!
 
Bevor wir nun weitermachen: Welche FreeBSD-Version ist das? 8.x, 9.x und 10.x unterscheiden sich in Sachen Compiler-Toolchain doch recht deutlich.
 
Na ja, grundsätzlich bin ich eher an neueren Versionen interessiert. Die Idee hinter dem Projekt ist es -std=c++11 Möglichkeiten für Multi-Threading auszutesten, insbesondere auch lock-freie Datenstrukturen zum Datenaustausch zwischen Lua-Threads (das sind coroutinen) zu verwenden. Also pro Core 1 Systemthread mit einem Lua-State + X Lua-Threads. Im Grunde also so etwas ähnliches wie Erlang mit einem C++ Scheduler und Lua-Prozessen.

Ich denke wir können es bei FBSD 10.0 und GCC und CLANG sowie statische und dynamische Libs lassen,- das wird auch so noch kompliziert genug. Makefile und Strukturierung der Dateien möchte ich nur so machen dass das evtl. später auf andere Platformen übertragbar ist, ich mir also nicht von vorneherein irgendwelche Limitierungen einbaue. Man weiß ja nie zu was solche Projekte am Ende führen, - wenn denn überhaupt zu etwas! :rolleyes:
 
Wenn man die Makefiles selbst schreiben möchte (wozu ich raten würde) und der Code auf einer Menge unterschiedlicher Systeme eingesetzt werden soll, ist gmake eigentlich immer die richtige Wahl.
Bei gmake geb ich dir ja widerstandslos recht, aber die Sache das man Makefiles selber schreiben möchte, versteh ich nicht ganz. Gerade die Syntax ist ja mehr aus grausam. Warum nicht auf cmake oder qmake zurückgreifen?
 
Nach einigen Erfahrungen mit diversen High-Level Tools neige ich auch eher dazu Low-Level-Tools zu verwenden - hier also gmake - insbesondere da meine Sachen ja nicht so furchtbar umfangreich sind. Im Grunde ist das so ähnlich wie der Unterschied zwischen IDE einerseits, Text-Editor + Shell andererseits. In die High-Level-Tools muss man sich auch immer erst einarbeiten, ist dann von deren Entwicklungsstand abhängig und meist hakt es dann auch irgendwo und man muss sich das Eingemachte genauer anschauen was nicht selten ein Makefile ist.

Ich denke das sind einerseits Geschmacksfragen, hängt andererseits von dem ab was man macht. Und ich tanze ganz gerne auf verschiedenen Hochzeiten, nicht nur bei C++ & Co. Bei wirklich umfangreichen Projekten, bei denen auch mehrere Leute beteilgt sind, mögen Cmake & Co ihre Vorzüge haben, aber in dieser Liga spiele ich nicht.

Klar, der make syntax ist ziemlich zum ..., aber das sind Shell-Skripte auch. Alles Uraltkram aus dem letzten Jahrtausend, aber das funktioniert offenbar immer noch ganz gut was man nicht von allen Tools sagen kann.
 
Ich mag das config.mk-Prinzip, wie z.B. bei den suckless-Projekten.
Viele CXXFLAGS sind leider compilerabhängig, mit einer config.mk kann man dann alles konfigurieren, wie man es möchte,
und wenn man das System wechselt, dann kann man umstellen was man braucht.

Mein Lieblingsbeispiel dafür sind die Warnungen von g++ und clang++,
bei ersterem nutze ich „-Wall -Wextra“ und bei zweiterem „-Weverything -Wno-padding -Wno-c++98-compat“.
Von unterschiedlichen Optionen mal ganz zu schweigen.
Eine andere gute Alternative sind IMO unterschiedliche Makefiles für unterschiedliche Plattformen.
 
Das mit den untershiedlichenn Compilern/Platformen habe ich jetzt so gelöst wie bei Lua. Natürlich kann man das Prinzip noch erweitern. Ich denke dass das so akzeptable ist.
Code:
#  USAGE:
#  gmake [<taget>]
#  [SHELL=<name of shell>]
#  [GCC | CLANG=<compiler version>]
#  PLATFORM=[<your platform>]
#  OR
#  edit the variables below as needed,
#  notably the default platform.
#
#   Use "gmake echo ... " to get your settings.
#
#   For currently unsupported PLATFORMS or
#   special requirements add a new PLATFORM.

# default platform
PLATFORM ?= freebsd10-gcc

# supported platforms
PLATS := freebsd10-gcc  freebsd10-clang

# executable names
SHELL_NAME ?= start
LIB_NAME ?= xyz

# compiler versions
GCC ?= gcc49
CLANG ?= clang

#-----------------------------
#  tools
#-----------------------------
MKDIR := mkdir -pv
RM := rm -rf

#-----------------------------
#  PLATFORM settings
#-----------------------------

ifeq ($(PLATFORM),"")
MSG := Select one of [$(PLATS)] OR add another one to suit your needs
$(error Undefined PLATFORM: $(MSG) )
else
### FreeBSD ###
# gcc
ifeq ($(PLATFORM),freebsd10-gcc)
CXX = $(GCC)
CXXFLAGS := -Wall -fPIC -O2 -Iinc
LDFLAGS:= -lstdc++
endif
# clang
ifeq ($(PLATFORM),freebsd10-clang)
CXX := clang
CXXFLAGS :=
LDFLAGS :=
endif
endif
Womit ich noch nicht weiter gekommen bin ist wie man die .o files in ein extra build directory bekommt und nicht wie jetzt alle auf dem toplevel generiert werden.

Ich verwende z.Z. nur gcc, clang kommt später. Und mit den Compiler Optionen muss ich mich noch beschäftigen. Dank WM ist meine Freizeit z.Z. etwas begrenzt ... :rolleyes:
 
Zurück
Oben