Compilar programas para Windows desde Linux – Parte 1

¿Eres un usuario de Linux y disfrutas haciendo tus programitas en C o C++ pero quieres compartirlos con el resto del mundo que seguramente usa Windows?. Compartirles el código es una posibilidad pero el usuario promedio no sabría qué hacer con él así que tus opciones se reducen a entregarles un ejecutable listo para usar en Windows. Por suerte, compilar tus programas para Windows es relativamente fácil en Linux, sobre todo si alguna vez usaste MinGW en Windows. MinGW es, por así decirlo, el gcc para Windows. MinGW ofrece compiladores para C y C++ (entre otros) que generan archivos ejecutables .exe para Windows así que, como te imaginarás, solamente tenemos que instalar MinGW para poder usarlo para generar ejecutables para Windows desde nuestra distro de Linux. Para instalar MinGW en Debian Stretch, basta con instalar el paquete mingw-w64 desde el gestor de paquetes.

Una vez que se ha instalado el paquete mingw-w64 podemos usar el compilador de MinGW para generar archivos ejecutables para Windows. En Debian Stretch, MinGW instala dos versiones del compilador para Windows, x86_64-w64-mingw32-gcc para generar ejecutables de 64 bits y i686-w64-mingw32-gcc para generar ejecutables de 32 bits. Puedes probar cualquiera de estos compiladores para compilar, por ejemplo, el ‘Hello World’:

monstruosoft@debian:~/code$ i686-w64-mingw32-gcc hello-world.c
monstruosoft@debian:~/code$ wine a.exe 
Hello world!

El comando anterior generará un ejecutable para Windows llamado a.exe (recuerda que puedes indicar el nombre del archivo de salida usando la opción -o de gcc).

Compilar el ‘Hello World’ es un buen comienzo pero la mayor parte del tiempo desearás compilar programas más complejos. Por supuesto, puedes usar las opciones -L -I -l del compilador para especificar las rutas y las librerías requeridas por tu programa pero muy probablemente preferirás usar un software como CMake. Por suerte, CMake tiene la opción de usar un archivo para definir un toolchain, es decir que puedes definir en un archivo los compiladores que deseas usar para compilar un proyecto; lo que esto quiere decir es que puedes usar el mismo archivo CMakeLists.txt para compilar de forma nativa para Linux o bien usar la opción de compilación cruzada para generar ejecutables para Windows con la ayuda de un archivo de toolchain. Lo anterior puede sonar más complicado de lo que en realidad es. Un archivo de toolchain para Windows luce de la siguiente manera:

# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows)

# which compilers to use for C and C++
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)

# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32 /home/monstruosoft/i686-mingw32)

# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Guarda el archivo anterior como toolchain-mingw32.cmake (nota que debes ajustar la ruta para tu carpeta local de MinGW, en este caso estoy usando /home/monstruosoft/i686-mingw32 pero puedes usar cualquier nombre que quieras). Una vez que has guardado este archivo, puedes usarlo para indicarle a CMake qué toolchain quieres utilizar. Si llamas CMake con los argumentos habituales, encontrará los compiladores definidos en el sistema y generará un ejecutable nativo para Linux; si llamas CMake con la opción -DCMAKE_TOOLCHAIN_FILE=toolchain-mingw32.cmake, CMake usará el toolchain definido en ese archivo y generará un ejecutable para Windows.

Hora de ver lo anterior en acción. El siguiente es el ćodigo para nuestro programa de ‘Hello World’:

Un archivo CMakeLists.txt típico para compilar ese programa sería el siguiente:

ADD_EXECUTABLE(hello hello-world.c)

Para compilar un ejecutable nativo para Linux usaríamos CMake de la siguiente manera:

monstruosoft@debian:~/code$ mkdir build
monstruosoft@debian:~/code$ cd build/
monstruosoft@debian:~/code/build$ cmake ..
-- The C compiler identification is GNU 6.3.0
-- The CXX compiler identification is GNU 6.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/monstruosoft/code/build
monstruosoft@debian:~/code/build$ make
Scanning dependencies of target hello
[ 50%] Building C object CMakeFiles/hello.dir/hello-world.c.o
[100%] Linking C executable hello
[100%] Built target hello
monstruosoft@debian:~/code/build$ ./hello 
Hello world!

Ahora, para compilar un ejecutable para Windows solamente debemos usar CMake de la siguiente manera:

monstruosoft@debian:~/code$ mkdir build-win
monstruosoft@debian:~/code$ cd build-win/
monstruosoft@debian:~/code/build-win$ cmake -DCMAKE_TOOLCHAIN_FILE=~/toolchain-mingw32.cmake ..
-- The C compiler identification is GNU 6.3.0
-- The CXX compiler identification is GNU 6.3.0
-- Check for working C compiler: /usr/bin/i686-w64-mingw32-gcc
-- Check for working C compiler: /usr/bin/i686-w64-mingw32-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/i686-w64-mingw32-g++
-- Check for working CXX compiler: /usr/bin/i686-w64-mingw32-g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/monstruosoft/code/build-win
monstruosoft@debian:~/code/build-win$ make
Scanning dependencies of target hello
[ 50%] Building C object CMakeFiles/hello.dir/hello-world.c.obj
[100%] Linking C executable hello.exe
[100%] Built target hello
monstruosoft@debian:~/code/build-win$ file hello.exe 
hello.exe: PE32 executable (console) Intel 80386, for MS Windows
monstruosoft@debian:~/code/build-win$ wine hello.exe 
Hello world!

Como puedes ver, simplemente especificar el archivo de toolchain bastó para que CMake detectara las versiones de los compiladores de MinGW y generara el ejecutable para Windows de forma correcta. En teoría, un archivo CMakeLists.txt bien estructurado debería funcionar sin modificaciones tanto para compilar nativamente en Linux como para compilar para Windows usando el toolchain de MinGW. En la parte 2 de este post veremos si lo anterior es verdad para programas más complejos que un ‘Hello world’.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s