OS detecting makefile
je travaille régulièrement sur plusieurs ordinateurs différents et plusieurs systèmes d'exploitation différents, qui sont Mac OS X, Linux, ou Solaris. Pour le projet sur lequel je travaille, je sors mon code d'un dépôt Git distant.
j'aime pouvoir travailler sur mes projets quel que soit le terminal où je suis. Jusqu'à présent, j'ai trouvé des moyens de contourner les changements OS en changeant le makefile chaque fois que je change d'ordinateur. Cependant, c'est fastidieux et provoque un tas de maux de tête.
Comment puis-je modifier mon makefile pour qu'il détecte quel OS j'utilise et modifie la syntaxe en conséquence?
voici le makefile:
cc = gcc -g
CC = g++ -g
yacc=$(YACC)
lex=$(FLEX)
all: assembler
assembler: y.tab.o lex.yy.o
$(CC) -o assembler y.tab.o lex.yy.o -ll -l y
assembler.o: assembler.c
$(cc) -o assembler.o assembler.c
y.tab.o: assem.y
$(yacc) -d assem.y
$(CC) -c y.tab.c
lex.yy.o: assem.l
$(lex) assem.l
$(cc) -c lex.yy.c
clean:
rm -f lex.yy.c y.tab.c y.tab.h assembler *.o *.tmp *.debug *.acts
12 réponses
il y a déjà beaucoup de bonnes réponses ici, mais je voulais partager un exemple plus complet que les deux:
- n'assume pas
uname
existe sur Windows - détecte également le processeur
les CCFLAGS définis ici ne sont pas nécessairement recommandés ou idéaux; ils sont exactement ce que le projet auquel j'ajoutais OS/CPU auto-detection s'est avéré utiliser.
ifeq ($(OS),Windows_NT)
CCFLAGS += -D WIN32
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
CCFLAGS += -D AMD64
else
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
CCFLAGS += -D AMD64
endif
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
CCFLAGS += -D IA32
endif
endif
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CCFLAGS += -D LINUX
endif
ifeq ($(UNAME_S),Darwin)
CCFLAGS += -D OSX
endif
UNAME_P := $(shell uname -p)
ifeq ($(UNAME_P),x86_64)
CCFLAGS += -D AMD64
endif
ifneq ($(filter %86,$(UNAME_P)),)
CCFLAGS += -D IA32
endif
ifneq ($(filter arm%,$(UNAME_P)),)
CCFLAGS += -D ARM
endif
endif
la commande uname ( http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/uname.1.html ) sans paramètres devrait vous indiquer le nom du système d'exploitation. J'utiliserais ça, puis je ferais des conditionnels basés sur la valeur de retour.
exemple
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
# do something Linux-y
endif
ifeq ($(UNAME), Solaris)
# do something Solaris-y
endif
si vous n'avez pas besoin de choses sophistiquées comme fait par Git LFS , vous pouvez détecter le système d'exploitation en utilisant juste deux astuces simples:
- variable d'environnement
OS
- puis la commande
uname -s
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell uname -s)
endif
ou un moyen plus sûr, sinon Windows et la commande uname
n'est pas disponible:
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell sh -c 'uname -s 2>/dev/null || echo not')
endif
alors vous pouvez sélectionner les éléments pertinents en fonction de detected_OS
:
ifeq ($(detected_OS),Windows)
CFLAGS += -D WIN32
endif
ifeq ($(detected_OS),Darwin) # Mac OS X
CFLAGS += -D OSX
endif
ifeq ($(detected_OS),Linux)
CFLAGS += -D LINUX
endif
ifeq ($(detected_OS),GNU) # Debian GNU Hurd
CFLAGS += -D GNU_HURD
endif
ifeq ($(detected_OS),GNU/kFreeBSD) # Debian kFreeBSD
CFLAGS += -D GNU_kFreeBSD
endif
ifeq ($(detected_OS),FreeBSD)
CFLAGS += -D FreeBSD
endif
ifeq ($(detected_OS),NetBSD)
CFLAGS += -D NetBSD
endif
ifeq ($(detected_OS),DragonFly)
CFLAGS += -D DragonFly
endif
ifeq ($(detected_OS),Haiku)
CFLAGS += -D Haiku
endif
s'il vous Plaît voir aussi cette réponse détaillée sur l'importance de uname -s
mieux que uname -o
.
L'utilisation de OS
(au lieu de uname -s
) simplifie l'algorithme d'identification. Vous pouvez toujours utiliser uniquement uname -s
, mais vous devez traiter avec if/else
blocs pour vérifier tous les MinGW, Cygwin, etc. variation.
Note: la variable d'environnement OS
est toujours définie à "Windows_NT"
sur toute plate-forme Windows (voir Variables D'environnement Windows sur Wikipedia ). Une alternative à OS
est la variable d'environnement MSVC
(elle vérifie la présence de MS Visual Studio , voir exemple utilisant Visual C++ ).
ci-dessous, je fournis un exemple complet en utilisant make
et gcc
pour construire une bibliothèque partagée: *.so
ou *.dll
selon la plate-forme. L'exemple est aussi simple que possible pour être plus compréhensible: -)
pour installer make
et gcc
sur Windows voir Cygwin ou MinGW .
mon exemple est basé sur cinq fichiers
├── lib
│ └── Makefile
│ └── hello.h
│ └── hello.c
└── app
└── Makefile
└── main.c
Ne pas oublier: les fichiers Makefile
sont en retrait à l'aide de tableaux.
les deux fichiers Makefile
1. lib/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = hello.dll
endif
ifeq ($(uname_S), Linux)
target = libhello.so
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -fPIC -o $@
# -c $< => $< is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o $@ => $@ is the target => Output file (-o) is hello.o
$(target): hello.o
gcc $^ -shared -o $@
# $^ => $^ expand to all prerequisites (after ':') => hello.o
# -shared => Generate shared library
# -o $@ => Output file (-o) is $@ (libhello.so or hello.dll)
2. app/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = app.exe
endif
ifeq ($(uname_S), Linux)
target = app
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -I ../lib -o $@
# -c $< => compile (-c) $< (first file after :) = main.c
# -I ../lib => search headers (*.h) in directory ../lib
# -o $@ => output file (-o) is $@ (target) = main.o
$(target): main.o
gcc $^ -L../lib -lhello -o $@
# $^ => $^ (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o $@ => output file (-o) is $@ (target) = "app.exe" or "app"
pour en savoir plus, lire Variables automatiques documentation comme indiqué par fci .
le code source
- lib/hello.h
#ifndef HELLO_H_
#define HELLO_H_
const char* hello();
#endif
- lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
- app/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
"1519870920 de" construction
fixer le copier-coller de Makefile
(remplacer les espaces avant par un tableau).
> sed 's/^ */\t/' -i */Makefile
la commande make
est la même sur les deux plateformes. La sortie donnée est sur des os de type Unix:
> make -C lib
make: Entering directory '/tmp/lib'
gcc -c hello.c -fPIC -o hello.o
# -c hello.c => hello.c is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o hello.o => hello.o is the target => Output file (-o) is hello.o
gcc hello.o -shared -o libhello.so
# hello.o => hello.o is the first after ':' => Link hello.o
# -shared => Generate shared library
# -o libhello.so => Output file (-o) is libhello.so (libhello.so or hello.dll)
make: Leaving directory '/tmp/lib'
> make -C app
make: Entering directory '/tmp/app'
gcc -c main.c -I ../lib -o main.o
# -c main.c => compile (-c) main.c (first file after :) = main.cpp
# -I ../lib => search headers (*.h) in directory ../lib
# -o main.o => output file (-o) is main.o (target) = main.o
gcc main.o -L../lib -lhello -o app
# main.o => main.o (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o app => output file (-o) is app.exe (target) = "app.exe" or "app"
make: Leaving directory '/tmp/app'
La course
l'application exige de savoir où est la bibliothèque partagée.
sur Windows, une solution simple est de copier la bibliothèque où l'application est:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
sur les os de type Unix, vous pouvez utiliser le LD_LIBRARY_PATH
variable d'environnement:
> export LD_LIBRARY_PATH=lib
exécuter la commande sur Windows:
> app/app.exe
hello
exécutez la commande sur des os de type Unix:
> app/app
hello
j'expérimentais récemment pour répondre à cette question que je me posais. Voici mes conclusions:
puisque sous Windows, vous ne pouvez pas être sûr que la commande uname
est disponible, vous pouvez utiliser gcc -dumpmachine
. Cela affichera la cible du compilateur.
il peut y avoir aussi un problème lorsque vous utilisez uname
si vous voulez faire une compilation croisée.
voici un exemple de liste de sortie possible de gcc -dumpmachine
:
- mingw32
- i686-pc-cygwin
- x86_64-redhat-linux
vous pouvez vérifier le résultat dans le makefile comme ceci:
SYS := $(shell gcc -dumpmachine)
ifneq (, $(findstring linux, $(SYS)))
# Do Linux things
else ifneq(, $(findstring mingw, $(SYS)))
# Do MinGW things
else ifneq(, $(findstring cygwin, $(SYS)))
# Do Cygwin things
else
# Do things for others
endif
cela a bien fonctionné pour moi, mais je ne suis pas sûr que ce soit un moyen fiable d'obtenir le type de système. Au moins, il est fiable sur MinGW et c'est tout ce dont j'ai besoin car il ne nécessite pas d'avoir la commande uname
ou MSYS dans Windows.
pour résumer, uname
vous donne le système sur que vous compilez, et gcc -dumpmachine
vous donne le système pour que vous compilez.
le git makefile contient de nombreux exemples de gestion sans autoconf/automake, mais fonctionne encore sur une multitude de plateformes unixy.
mise à jour: je considère maintenant cette réponse comme obsolète. J'ai posté une nouvelle solution parfaite.
si votre makefile peut être exécuté sur des fenêtres autres que Cygwin, uname
peut ne pas être disponible. C'est embarrassant, mais c'est une solution potentielle. Vous devez vérifier pour Cygwin d'abord pour l'exclure, parce qu'il a des fenêtres dans sa variable d'environnement PATH
trop.
ifneq (,$(findstring /cygdrive/,$(PATH)))
UNAME := Cygwin
else
ifneq (,$(findstring WINDOWS,$(PATH)))
UNAME := Windows
else
UNAME := $(shell uname -s)
endif
endif
C'est le travail que GNU automake / autoconf est conçu pour résoudre. Vous pourriez vouloir examiner.
alternativement, vous pouvez définir des variables d'environnement sur vos différentes plateformes et faire dépendre votre Makefile de ces variables.
j'ai rencontré ce problème aujourd'hui et J'en avais besoin sur Solaris alors voici une méthode standard POSIX pour le faire (quelque chose de très proche).
#Detect OS
UNAME = `uname`
# Build based on OS name
DetectOS:
-@make $(UNAME)
# OS is Linux, use GCC
Linux: program.c
@SHELL_VARIABLE="-D_LINUX_STUFF_HERE_"
rm -f program
gcc $(SHELL_VARIABLE) -o program program.c
# OS is Solaris, use c99
SunOS: program.c
@SHELL_VARIABLE="-D_SOLARIS_STUFF_HERE_"
rm -f program
c99 $(SHELL_VARIABLE) -o program program.c
une autre façon de le faire est d'utiliser un script" configure". Si vous en utilisez déjà un avec votre makefile, vous pouvez utiliser une combinaison de uname et sed pour que les choses s'arrangent. D'abord, dans votre script, faites:
UNAME=uname
ensuite, pour mettre ceci dans votre Makefile, commencez par Makefile.dans lequel devrait avoir quelque chose comme
UNAME=@@UNAME@@
dedans.
utilisez la commande sed suivante dans votre script de configuration après le UNAME=uname
.
sed -e "s|@@UNAME@@|$UNAME|" < Makefile.in > Makefile
maintenant votre makefile devrait avoir UNAME
défini comme désiré. Si/elif / else déclarations sont tout ce qui reste!
Voici une solution simple qui vérifie si vous êtes dans un environnement Windows ou posix-like (Linux / Unix / Cygwin / Mac):
ifeq ($(shell echo "check_quotes"),"check_quotes")
WINDOWS := yes
else
WINDOWS := no
endif
Il tire avantage du fait que l'écho existe à la fois dans les environnements posix et Windows, et que dans Windows le shell ne filtre pas les citations.
notez que les Makefiles sont extrêmement sensibles à l'espacement. Voici un exemple de Makefile qui exécute une commande supplémentaire sur OS X et qui fonctionne sur OS X et Linux. Dans l'ensemble, cependant, autoconf/automake est la voie à suivre pour tout ce qui n'est pas trivial.
UNAME := $(shell uname -s) CPP = g++ CPPFLAGS = -pthread -ansi -Wall -Werror -pedantic -O0 -g3 -I /nexopia/include LDFLAGS = -pthread -L/nexopia/lib -lboost_system HEADERS = data_structures.h http_client.h load.h lock.h search.h server.h thread.h utility.h OBJECTS = http_client.o load.o lock.o search.o server.o thread.o utility.o vor.o all: vor clean: rm -f $(OBJECTS) vor vor: $(OBJECTS) $(CPP) $(LDFLAGS) -o vor $(OBJECTS) ifeq ($(UNAME),Darwin) # Set the Boost library location install_name_tool -change libboost_system.dylib /nexopia/lib/libboost_system.dylib vor endif %.o: %.cpp $(HEADERS) Makefile $(CPP) $(CPPFLAGS) -c $
j'ai finalement trouvé la solution parfaite qui résout ce problème pour moi.
ifeq '$(findstring ;,$(PATH))' ';'
UNAME := Windows
else
UNAME := $(shell uname 2>/dev/null || echo Unknown)
UNAME := $(patsubst CYGWIN%,Cygwin,$(UNAME))
UNAME := $(patsubst MSYS%,MSYS,$(UNAME))
UNAME := $(patsubst MINGW%,MSYS,$(UNAME))
endif
la variable UNAME est définie à Linux, Cygwin, MSYS, Windows, FreeBSD, NetBSD (ou probablement Solaris, Darwin, OpenBSD, AIX, HP-UX), ou Unknown. Il peut ensuite être comparé dans le reste du Makefile pour séparer les variables et les commandes sensibles à L'OS.
la clé est que Windows utilise des points-virgule pour séparer les chemins dans la variable PATH alors que tout le monde utilise des virgules. (Il est possible de créer un répertoire Linux avec un ';' dans le nom et de l'ajouter au chemin, ce qui briserait cela, mais qui ferait une telle chose?) Cela semble être la méthode la moins risquée pour détecter les fenêtres natives car elle n'a pas besoin d'un appel shell. Le chemin Cygwin et MSYS utilisent des colons donc uname est appelé pour eux.
notez que la variable d'environnement OS peut être utilisée pour détecter les fenêtres, mais pas pour distinguer entre Cygwin et les fenêtres natives. L'essai pour l'écho des citations fonctionne, mais il nécessite un appel shell.
malheureusement, Cygwin ajoute quelques informations de version à la sortie de uname , donc j'ai ajouté les appels 'patsubst' pour le changer en 'Cygwin'. En outre, uname for MSYS a en fait trois sorties possibles à partir de MSYS ou MINGW, mais j'utilise aussi patsubst pour transformer tout en "MSYS".
S'il est important de distinguer entre les systèmes Windows natifs avec et sans certains uname.exe sur le chemin, cette ligne peut être utilisée à la place de la simple assignation:
UNAME := $(shell uname 2>NUL || echo Windows)
bien sûr dans tous les cas GNU make est nécessaire, ou un autre make qui supporte les fonctions utilisées.