0

I am trying to compile the following code twice, once with -m32 and once without:

// File mylib.cc

#include <iostream>

void print_int_size() {
  std::cout << sizeof(int*) << std::endl;
}


// File main.cc

void print_int_size();

int main() {
  print_int_size();
  return 0;
}

I have the following in my CMakeLists.txt:

project (Link32b VERSION 0.91 LANGUAGES CXX)


add_library ( mylib STATIC mylib.cc )
add_library ( mylib_32b STATIC mylib.cc )

target_compile_options ( mylib_32b PUBLIC -m32 )

add_executable ( main main.cc )
add_executable ( main_32b main.cc )

target_compile_options ( main_32b PRIVATE -m32 )

target_link_libraries ( main PRIVATE mylib )
target_link_libraries ( main_32b PRIVATE mylib_32b )

I get the following output when compiling (similar with gcc):

Scanning dependencies of target mylib
[ 12%] Building CXX object CMakeFiles/mylib.dir/mylib.cc.o
[ 25%] Linking CXX static library libmylib.a
[ 25%] Built target mylib
Scanning dependencies of target main
[ 37%] Building CXX object CMakeFiles/main.dir/main.cc.o
[ 50%] Linking CXX executable main
[ 50%] Built target main
Scanning dependencies of target mylib_32b
[ 62%] Building CXX object CMakeFiles/mylib_32b.dir/mylib.cc.o
[ 75%] Linking CXX static library libmylib_32b.a
[ 75%] Built target mylib_32b
Scanning dependencies of target main_32b
[ 87%] Building CXX object CMakeFiles/main_32b.dir/main.cc.o
[100%] Linking CXX executable main_32b
ld: warning: ignoring file CMakeFiles/main_32b.dir/main.cc.o, file was built for i386 which is not the architecture being linked (x86_64): CMakeFiles/main_32b.dir/main.cc.o
ld: warning: ignoring file libmylib_32b.a, file was built for archive which is not the architecture being linked (x86_64): libmylib_32b.a
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [main_32b] Error 1
make[1]: *** [CMakeFiles/main_32b.dir/all] Error 2
make: *** [all] Error 2

What am I missing here?

===

UPDATE: It is curious that setting CMAKE_CXX_FLAGS to include -m32 makes the example work. However, I would like to get it done without setting variables, i.e., follow the mordern target-based approach.

dow
  • 513
  • 2
  • 5
  • 16
  • 1
    Looks like you need to specify flags `-m32` also for the **linker**, like in [that answer](https://stackoverflow.com/a/19310455/3440745). Note, that STATIC library doesn't require linker flags, because it is not linked actually. – Tsyvarev Aug 01 '18 at 16:43
  • Seems that the answer is that the `-m32` option is not `INTERFACE`d out to the linker. This solved my problem; if you add a response I'll be happy to accept it. – dow Aug 01 '18 at 17:03
  • Possible duplicate of [The proper way of forcing a 32-bit compile using CMake](https://stackoverflow.com/questions/5805874/the-proper-way-of-forcing-a-32-bit-compile-using-cmake) – Tsyvarev Aug 01 '18 at 17:09
  • The answer to my question can indeed be inferred from [this reply](https://stackoverflow.com/a/19310455/746341) but (a) the first few answers on that thread are not at all what I am looking for and (b) the question itself is different. That user is asking how to compile their own code with `-m32`, whereas I am asking how to pass the `-m32` flag to the linker. An additional component of my question is that it would seem reasonable to assume that the option `-m32` would be made available to the linker since it was a `PUBLIC` property of the linked library, but that is apparently not the case. – dow Aug 02 '18 at 16:20
  • "the first few answers on that thread are not at all what I am looking for" - Er? The very first answer tells to use LINK_FLAGS. As for setting COMPILE_FLAGS property - it is the same as `target_compile_options`. "That user is asking how to compile their own code with `-m32`, whereas I am asking how to pass the `-m32` flag to the linker." - Your whole problem is to compile with `-m32`, and the referenced question asks exactly about that. Of course, not all answers suited for you, but what you want is covered by the referenced question. – Tsyvarev Aug 02 '18 at 16:52
  • The [very first answer](https://stackoverflow.com/a/5809477/746341) talks about `TRY_RUN` and contains nothing about `LINK_FLAGS`. The next two responses talk about toolchain files, and that's not at all what I am looking for. "Your whole problem is to compile with -m32" -- no it is not. My problem was how to *link* with -m32, and specifically that `target_link_libraries` does not work for linking as it does for compiling as far as `PUBLIC` properties/flags go. – dow Aug 02 '18 at 17:35
  • Em, you point to the answer which almost the last (according to score). The first scored answer is https://stackoverflow.com/a/19310455/3440745. You insist, that your question is about the linking only, but the referenced one is about the linking+compiling. (Do not treat the words "compile" *literally* - the asker wants it code to be built successfully). – Tsyvarev Aug 02 '18 at 17:40
  • My question showcases the issue with a most simple target-based example. I feel that the one you refer to is phrased in way too general of a manner, which is why I did not find the correct answer by searching on so. In your linked question, the phrasing hints at using `CMAKE_CXX_FLAGS` etc. (which I know how to do). I feel that my question is useful for people who wonder why `target_link_libraries` does not pass all necessary flags all the way through. Also, by "first" I did not realize you meant the "highest-scored" (my bad). – dow Aug 02 '18 at 17:49
  • I would be happy to rephrase the question if that would help make the distinction more obvious. – dow Aug 02 '18 at 17:59
  • "I feel that the one you refer to is phrased in way too general of a manner, which is why I did not find the correct answer by searching on so" - No problem, this is not your bad. I think you misunderstand "duplicate" as something definitely *bad*. But [it is not](https://stackoverflow.com/help/duplicates)! The true purpose of "duplicating" is to have all questions, **asking** about the **same thing**, to be binded with the **same answers**. I agree that your wording is quite different, and this is nice: having differently phrased questions there is more chance for other people to find answer. – Tsyvarev Aug 02 '18 at 18:26

3 Answers3

2

The -m32 flag is not "inherited" for linking purposes:

target_compile_options ( <lib> PUBLIC -m32 )
target_link_libraries ( <target> PRIVATE <lib> ) // Does not link with `-m32`.

Note that the above causes <target> to be compiled with -m32, since target_link_libraries "inherits" PUBLIC compilation option from <lib>. However, the flag is not passed to the linker.

Moreover, there is no target_link_options command, and so, one cannot insert the line target_link_options ( <link> PUBLIC -m32 ) to solve the problem.

Instead, per this answer (modified slightly), the correct approach is

target_compile_options ( <lib> PUBLIC -m32 )
set_target_properties ( <target> PROPERTIES LINK_FLAGS -m32 )
target_link_libraries ( <target> PRIVATE <lib> )
dow
  • 513
  • 2
  • 5
  • 16
1

To streamline the maintenance of your project, I suggest you keep the build system as simple as possible and instead configure and build the project twice:

  • one build for the 64-bit binaries
  • one build for the 32-bit binaries:

    • by setting the CXXFLAGS and CFLAGS environment variables to -m32 (approach 1)
    • or by setting the three environment variables AS, CXX and CC (approach 2)

Simplify your example project

Make also sure to add cmake_minimum_required, otherwise you would get the error VERSION not allowed unless CMP0048 is set to NEW.

cmake_minimum_required(VERSION 3.10)
project (Link VERSION 0.91 LANGUAGES CXX)
add_library ( mylib STATIC mylib.cc )
add_executable ( main main.cc )
target_link_libraries ( main PRIVATE mylib )

By having a simpler build system that do not hard-code assumption about the toolchain, you implicitly enable support for cross-platform and different environment like ARM, etc ... it can also make the continuous integration easier. For example, on CircleCI, you would have two build jobs (one for 64-bit, one for 32-bit) both building a simple project.

install required i386 libraries

On Ubuntu, this could be done like this

sudo apt-get install \
  gcc-multilib \
  g++-multilib \
  libc6:i386 \
  libstdc++6:i386

Other dependency would be installed using packageName:i386

Step-by-step: Approach 1

Assuming we have the following directory structure:

<root>
  |
  |-src
  |  |--- CMakeLists.txt
  |  |--- main.cc
  |  |--- mylib.cc
  |
  |-build
  |   |-- ...
  |
  |-build-32
      |-- ...

you could complie the 32-bit version by simply doing:

CFLAGS=-m32 CXXFLAGS=-m32 cmake -Hsrc -Bbuild-32

Step-by-step: Approach 2

The goal of the approach 2 is to introduce the idea of cross-compilation.

In the last section, you will then learn about dockcross/linux-32 docker images that internally applies the same principle.

Create three wrapper scripts for as, gcc and g++

Below are the content of three shell scripts:

  • i686-linux-gnu-as

#!/bin/bash exec as -m32 "$@"

  • i686-linux-gnu-gcc

#!/bin/bash exec gcc -m32 "$@"

  • i686-linux-gnu-g++

#!/bin/bash exec g++ -m32 "$@"

Compile

Assuming we have the following directory structure:

<root>
  |-bin
  |  |- i686-linux-gnu-as
  |  |- i686-linux-gnu-g++
  |  |- i686-linux-gnu-gcc
  |
  |-src
  |  |--- CMakeLists.txt
  |  |--- main.cc
  |  |--- mylib.cc
  |
  |-build
  |   |-- ...
  |
  |-build-32
      |-- ...

you would respectively do

64-bit
$ cmake -Hsrc -Bbuild; cmake --build ./build
-- The CXX compiler identification is GNU 5.2.1
-- 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: /tmp/scratch/build
[...]
[100%] Built target main

$ file ./build/main 
./build/main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e28e610f85cd4a2ab29e38ed58c1cb928f4aaf33, not stripped

$ ./build/main 
4
32-bit
$ CXX=$(pwd)/bin/i686-linux-gnu-g++ \
  CC=$(pwd)/bin/i686-linux-gnu-gcc \
  AS=$(pwd)/bin/i686-linux-gnu-as linux32 \
  bash -c "cmake -Hsrc -Bbuild-32; cmake --build ./build-32/"
-- The CXX compiler identification is GNU 5.2.1
-- Check for working CXX compiler: /tmp/scratch/bin/i686-linux-gnu-g++
-- Check for working CXX compiler: /tmp/scratch/bin/i686-linux-gnu-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
[...]
[100%] Built target main

$ file ./build-32/main 
./build-32/main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b7f5781f66a28e28d28eda2b798b671c1e89e22a, not stripped

$ ./build-32/main 
4

Now to understand why sizeof(int) is the same on both the 64 and 32-bit build, consider reading C/C++: sizeof(short), sizeof(int), sizeof(long), sizeof(long long), etc... on a 32-bit machine versus on a 64-bit machine

Streamlining the compilation using dockcross

Now, to easily compile to 32-bit you could also use the dockcross image dockcross/linux-x86. See https://github.com/dockcross/dockcross#readme

docker pull dockcross/linux-x86
docker run -ti --rm dockcross/linux-x86 > dockcross-linux-x86
chmod u+x dockcross-linux-x86

Then to compile, you would do:

dockcross-linux-x86  bash -c "cmake -Hsrc -Bbuild-32; cmake --build ./build-32/"
J-Christophe
  • 1,975
  • 15
  • 13
  • I'm pro on the 2 diff builds idea, but wouldn't be enough to modify the `CMAKE__FLAGS` (by adding `-m32` or not) on each case? (I'm not referring to the `multilib` installation, that's necessary). – compor Aug 02 '18 at 08:20
  • Instead of modifying `CMAKE_*FLAGS` variables, I instead suggest to set the environment variables `CXXFLAGS` and `CFLAGS` before configuring. This would ensure the `CMAKE_*FLAGS` are initialized as expected: `CFLAGS=-m32 CXXFLAGS=-m32 cmake -Hsrc -Bbuild-32` – J-Christophe Aug 02 '18 at 14:49
  • sorry, I'm confused; where does that happen in your answer? I cannot find the setting of `CXXFLAGS`. You just create wrapper scripts around the various tools, no? In other words, is the fuss of creating the wrapper necessary? Even in the answer linked by @Tsyvarev, the `CMAKE__FLAGS` and friends are used. Isn't this the most supple approach which also conforms with modern cmake practices? – compor Aug 02 '18 at 15:40
  • This gets the job done, but it is not what I was looking for. What answers my question is that there is no `target_link_options` akin to `target_compile_options`, and so the `LINK_FLAGS` property needs to be set using `set_target_properties`. I want to avoid having the user guide the compilation. – dow Aug 02 '18 at 15:58
  • Moreover, `sizeof(int)` was a mistake, I intended to say `sizeof(int*)` instead. I have edited my question accordingly. – dow Aug 02 '18 at 15:59
  • > re: is the fuss of creating the wrapper necessary? My initial answer was based on the approach we used in the dockcross/linux-x86 image. – J-Christophe Aug 03 '18 at 05:33
  • > the CMAKE__FLAGS and friends are used. Isn't this the most supple approach which also conforms with modern cmake practices? Setting CFLAGS and CXXFLAGS is the recommended approach, it basically ensure that the cache variables CMAKE__FLAGS are properly initialized only during the initial configuration. See https://gitlab.kitware.com/cmake/cmake/blob/ce309b624aaa756c802a3dfc581c410578f77d3b/Modules/CMakeCXXInformation.cmake#L190-194 – J-Christophe Aug 03 '18 at 05:37
  • By asking the user to set CMAKE__FLAGS, there is a risk of overwriting any flag set internally by your project (unless some specific measurew are taken to avoid this to happen) – J-Christophe Aug 03 '18 at 05:38
  • Last, by having a simpler build system that do not hardcode assumption about the toolchain, you would implicitly enable support for cross-platform and different environment like ARM, etc ... it would also make the continous integration easier. For example, on CircleCI, you would have two build jobs (one for 64-bit, one for 32-bit) both building a simple project. – J-Christophe Aug 03 '18 at 05:46
  • I edited the original answer to also include the approach setting CFLAGS and CXXFLAGS – J-Christophe Aug 05 '18 at 03:20
1

Addition to OP's answer: Cmake 3.13+ has target_link_options command therefore you can write: target_link_options(<target> PRIVATE "-m32")

cl0ne
  • 317
  • 5
  • 14