makefile os-agnostic os-detection

OS detectando makefile



os-agnostic os-detection (12)

Normalmente trabajo en varias computadoras diferentes y en diferentes sistemas operativos, que son Mac OS X, Linux o Solaris. Para el proyecto en el que estoy trabajando, extraigo mi código de un repositorio de git remoto.

Me gusta poder trabajar en mis proyectos sin importar en qué terminal esté. Hasta ahora, he encontrado maneras de sortear los cambios en el sistema operativo cambiando el makefile cada vez que cambio de computadora. Sin embargo, esto es tedioso y causa un montón de dolores de cabeza.

¿Cómo puedo modificar mi makefile para que detecte qué sistema operativo estoy usando y modifique la sintaxis en consecuencia?

Aquí está el 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


Detecta el sistema operativo utilizando dos simples trucos:

  • Primero la variable de entorno OS
  • Entonces el comando uname

ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10... detected_OS := Windows else detected_OS := $(shell uname) # same as "uname -s" endif

O una forma más segura, si no es en Windows y uname no disponible:

ifeq ($(OS),Windows_NT) detected_OS := Windows else detected_OS := $(shell sh -c ''uname 2>/dev/null || echo Unknown'') endif

Ken Jackson propone una alternativa interesante si desea distinguir Cygwin / MinGW / MSYS / Windows. Ver su respuesta que se parece a eso:

ifeq ''$(findstring ;,$(PATH))'' '';'' detected_OS := Windows else detected_OS := $(shell uname 2>/dev/null || echo Unknown) detected_OS := $(patsubst CYGWIN%,Cygwin,$(detected_OS)) detected_OS := $(patsubst MSYS%,MSYS,$(detected_OS)) detected_OS := $(patsubst MINGW%,MSYS,$(detected_OS)) endif

A continuación, puede seleccionar las cosas relevantes en función 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

Notas:

  • El comando uname es el mismo que uname -s porque la opción -s ( --kernel-name ) es la predeterminada. Vea por qué uname -s es mejor que uname -o .

  • El uso de OS (en lugar de uname ) simplifica el algoritmo de identificación. Aún puede usar únicamente uname , pero tiene que lidiar con if/else bloquea para verificar todas las variaciones de MinGW, Cygwin, etc.

  • La variable de entorno OS siempre se establece en "Windows_NT" en diferentes versiones de Windows (consulte %OS% variable de entorno en Wikipedia ).

  • Una alternativa del OS es la variable de entorno MSVC (verifica la presencia de MS Visual Studio , ver ejemplo usando Visual C ++ ).

A continuación proporciono un ejemplo completo utilizando make y gcc para construir una biblioteca compartida: *.so o *.dll según la plataforma. El ejemplo es lo más simple posible para que sea más comprensible.

Para instalar make y gcc en Windows, vea Cygwin o MinGW .

Mi ejemplo se basa en cinco archivos.

├── lib │ └── Makefile │ └── hello.h │ └── hello.c └── app └── Makefile └── main.c

Recordatorio: Makefile se sangra utilizando tabulación . Precaución al copiar y pegar debajo de los archivos de muestra.

Los dos archivos de 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://.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://.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"

Para obtener más información, lea la documentación de Variables automáticas como lo cfi .

El código fuente

- 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); }

La construcción

Corrija la copia y pegado de Makefile (reemplace los espacios iniciales por una tabulación).

> sed ''s/^ *//t/'' -i */Makefile

El comando make es el mismo en ambas plataformas. La salida dada es en sistemas operativos tipo 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 carrera

La aplicación requiere saber dónde está la biblioteca compartida.

En Windows, una solución simple es copiar la biblioteca donde está la aplicación:

> cp -v lib/hello.dll app `lib/hello.dll'' -> `app/hello.dll''

En sistemas operativos similares a Unix, puede usar la LD_LIBRARY_PATH entorno LD_LIBRARY_PATH :

> export LD_LIBRARY_PATH=lib

Ejecute el comando en Windows:

> app/app.exe hello

Ejecute el comando en sistemas operativos similares a Unix:

> app/app hello


Actualización: ahora considero que esta respuesta está obsoleta. Publiqué una nueva solución perfecta más abajo.

Si su makefile puede estar ejecutándose en Windows no Cygwin, es posible que uname no esté disponible. Eso es incómodo, pero esta es una solución potencial. Primero debes descartar a Cygwin para descartarlo, ya que también tiene WINDOWS en su PATH entorno PATH .

ifneq (,$(findstring /cygdrive/,$(PATH))) UNAME := Cygwin else ifneq (,$(findstring WINDOWS,$(PATH))) UNAME := Windows else UNAME := $(shell uname -s) endif endif


Aquí hay una solución simple que comprueba si está en un entorno de Windows o similar a posix (Linux / Unix / Cygwin / Mac):

ifeq ($(shell echo "check_quotes"),"check_quotes") WINDOWS := yes else WINDOWS := no endif

Aprovecha el hecho de que echo existe en entornos tanto posix como Windows, y que en Windows el shell no filtra las comillas.



Ese es el trabajo que el automake / autoconf GNU está diseñado para resolver. Es posible que desee investigarlos.

Alternativamente, puede establecer variables de entorno en sus diferentes plataformas y hacer que su Makefile sea condicional contra ellas.


Finalmente encontré la solución perfecta que resuelve este problema para mí.

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 se establece en Linux, Cygwin, MSYS, Windows, FreeBSD, NetBSD (o, presumiblemente, Solaris, Darwin, OpenBSD, AIX, HP-UX) o Desconocido. Luego puede compararse a lo largo del resto del Makefile para separar cualquier comando y variables sensibles al sistema operativo.

La clave es que Windows usa puntos y coma para separar las rutas en la variable PATH, mientras que todos los demás usan dos puntos. (Es posible hacer un directorio de Linux con un '';'' en el nombre y agregarlo a PATH, lo que rompería esto, pero ¿quién haría tal cosa?) Este parece ser el método menos arriesgado para detectar Windows nativo porque no necesita una llamada de shell Cygwin y MSYS PATH usan dos puntos, por lo que se llama uname para ellos.

Tenga en cuenta que la variable de entorno del sistema operativo se puede utilizar para detectar Windows, pero no para distinguir entre Cygwin y Windows nativo. Las pruebas para el eco de las citas funcionan, pero requieren una llamada de shell.

Desafortunadamente, Cygwin agrega alguna información de versión a la salida de uname , así que agregué las llamadas ''patsubst'' para cambiarlo a solo ''Cygwin''. Además, uname para MSYS en realidad tiene tres salidas posibles comenzando con MSYS o MINGW, pero también uso patsubst para transformar todo a solo "MSYS".

Si es importante distinguir entre los sistemas Windows nativos con y sin algunos uname.exe en la ruta, esta línea se puede usar en lugar de la asignación simple:

UNAME := $(shell uname 2>NUL || echo Windows)

Por supuesto, en todos los casos se requiere la creación de GNU, u otra marca que admita las funciones utilizadas.


Hace poco estaba experimentando para responder esta pregunta que me hacía a mí mismo. Aquí están mis conclusiones:

Ya que en Windows, no puede estar seguro de que el comando uname esté disponible, puede usar gcc -dumpmachine . Esto mostrará el objetivo del compilador.

También puede haber un problema al usar uname si desea realizar una compilación cruzada.

Aquí hay una lista de ejemplo de salida posible de gcc -dumpmachine :

  • mingw32
  • i686-pc-cygwin
  • x86_64-redhat-linux

Puede comprobar el resultado en el makefile así:

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

Funcionó bien para mí, pero no estoy seguro de que sea una forma confiable de obtener el tipo de sistema. Al menos es confiable acerca de MinGW y eso es todo lo que necesito, ya que no requiere tener el comando uname o el paquete MSYS en Windows.

Para resumir, uname le da el sistema en el que está compilando, y gcc -dumpmachine le da el sistema para el que está compilando.


Hoy me encontré con este problema y lo necesitaba en Solaris, por lo que aquí hay una forma estándar de POSIX para hacer esto (algo muy parecido a).

#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


Otra forma de hacerlo es usando un script de "configuración". Si ya está usando uno con su makefile, puede usar una combinación de uname y sed para que las cosas funcionen. Primero, en tu guión, haz:

UNAME=uname

Luego, para poner esto en tu Makefile, comienza con Makefile.in, que debería tener algo como

UNAME=@@UNAME@@

en eso.

Use el siguiente comando sed en su script de configuración después de UNAME=uname bit.

sed -e "s|@@UNAME@@|$UNAME|" < Makefile.in > Makefile

Ahora su makefile debe tener UNAME definido como deseado. Si las declaraciones de / elif / else son todo lo que queda!


Tenga en cuenta que los Makefiles son extremadamente sensibles al espaciado. Aquí hay un ejemplo de un Makefile que ejecuta un comando adicional en OS X y que funciona en OS X y Linux. En general, sin embargo, autoconf / automake es el camino a seguir para cualquier cosa que no sea 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 $


Ya hay muchas respuestas buenas aquí, pero quería compartir un ejemplo más completo que:

  • no asume que existe uname en Windows
  • también detecta el procesador

Los CCFLAGS definidos aquí no son necesariamente recomendados o ideales; son exactamente lo que el proyecto al que estaba agregando OS / CPU auto-detección estaba usando.

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


Git makefile contiene numerosos ejemplos de cómo administrar sin autoconf / automake, y aún así funciona en una multitud de plataformas unixy.