Reputation: 16486
Most of the information contained in this question can be obtained here:
A series of events have caused me to want to try and build a C++ application inside a Docker container, while using vcpkg
.
Here are a series of statements which should help provide context:
gcc
/g++
to compile the C++ codegcc
. It is something like Version 12. It does not support (at least some of) the C++ 23 features which I needgcc
, but I am hesitant to do so because this involves either temporarily changing the default compiler with apt update alternatives
or configuring the compiler version for the chosen build system manually. This doesn't seem like a good approach, and I have not had good experiences with this in the past. I would suggest using a Docker container is an easier approach.With that said, I cannot see a straightforward way to create a Docker container to do this.
I have one solution, which works, but it is not a good solution. The steps followed are described below.
vcpkg
on the hostgit clone https://github.com/microsoft/vcpkg.git
cd vcpkg && ./bootstrap-vcpkg.sh -disableMetrics
export VCPKG_ROOT=/path/to/vcpkg
export PATH=$VCPKG_ROOT:$PATH
vcpkg
mkdir myproject && cd myproject
vcpkg new --application
# created `vcpkg-configuration.json` and `vcpkg.json`
# example: add dependencies using vcpkg
vcpkg add port fmt
CMakeLists.txt
There are other build systems available. My understanding is that vcpkg
is compatiable with a range of build systems. I am using cmake
by default, not for any particular reason.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(myproject)
find_package(fmt CONFIG REQUIRED)
add_executable(myproject main.cpp)
target_link_libraries(myproject PRIVATE fmt::fmt)
main.cpp
main.cpp
can contain a minimal "hello world" example.
CMakePresets.json
and CMakeUserPresets.json
, and build# CMakePresets.json
{
"version": 2,
"configurePresets": [
{
"name": "vcpkg",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
# CMakeUserPresets.json
{
"version": 2,
"configurePresets": [
{
"name": "default",
"inherits": "vcpkg"
}
]
}
Note that this file (CMakeUserPresets.json
) differs from the example file provided in the documentation, shown below:
# CMakeUserPresets.json (example from documentation)
{
"version": 2,
"configurePresets": [
{
"name": "default",
"inherits": "vcpkg",
"environment": {
"VCPKG_ROOT": "<path to vcpkg>"
}
}
]
}
I tried changing
"VCPKG_ROOT": "<path to vcpkg>"
to
"VCPKG_ROOT": "$env{VCPKG_ROOT}"
however, this resulted in an error when trying to run
$ cmake --preset=default
CMake Error: Could not read presets from /home/user/myproject: Invalid macro expansion
Therefore I removed "environment"
, and found that it seems to work.
The next error I encountered was the following:
CMake was unable to find a build program corresponding to "Ninja".
Therefore I changed
"generator": "Ninja"
to
"generator": "Unix Makefiles"
in CMakePresets.json
.
Then the command ran ok: cmake --preset=default
.
Finally, to build:
cmake --build build
Note that if we were to repeat these steps inside a Docker container, the first step would be required, but none of the remaining steps are required.
In the first step, we installed vcpkg
, which would need to be done inside a Docker container, because it is unlikely we find an existing image which contains everything we want.
However, the remaining steps do not need to be repeated. We simply copy the required files into the container image.
There are two problem with this approach:
vcpkg
goes and downloads a file from github. This is caused by the command cmake --preset=default
.To explain the second point in more detial: The build of the Docker container image depends on some of the vcpkg
commands having been run before the container is built. This is because the container depends on the files vcpkg-configuration.json
and vcpkg.json
. These are generated by running some vcpkg
commands. To generate them, the host system requires an installation of vcpkg
.
It feels a bit "chicken and egg".
It would be possible to run these commands inside the container as part of the build process instead, however then vcpkg-configuration.json
and vcpkg.json
would be generated files rather than files stored under source control.
Here's a copy of the Dockerfile
.
FROM gcc:latest
WORKDIR /root
RUN apt update && apt install curl zip unzip tar cmake -y
RUN git clone https://github.com/microsoft/vcpkg.git
RUN cd vcpkg && ./bootstrap-vcpkg.sh -disableMetrics
ENV VCPKG_ROOT=/root/vcpkg
ENV PATH=$VCPKG_ROOT:$PATH
RUN mkdir /app
WORKDIR /app
COPY ./myproject ./myproject
WORKDIR /app/myproject
RUN cmake --preset=default
RUN cmake --build build
A .dockerignore
file is also required to prevent copying of the build
directory. Without this the build will error.
#.dockerignore
**/build
To run it
$ docker build -t myproject/myprojectbuild .
$ docker run --rm myproject/myprojectbuild
To summarize:
Upvotes: -5
Views: 106
Reputation: 158598
I could install a second version of gcc...
cmake supports a CC
environment variable to specify the location of the compiler. This is fairly common among non-hand-built build systems; GNU Automake definitely supports it, it is a standard variable in make
, and so on.
Before doing anything else, I'd try just using the alternate compiler you have installed on the host
export CC=gcc-14 CXX=g++-14
cmake --build
If you really want to run this build in a container, remember that a container (usually) has its own installation of a base Linux distribution. "Don't break Debian" is good advice, but if you did happen to break it in a container, you can just delete the container and start over. Using the Debian alternatives system would only update the default compiler in the container, and not affect the host at all.
This, plus some mechanical difficulties, means you'd typically avoid version-manager tools in a container environment. Install the single toolchain you need to build the single application that the container runs.
For this example I'll use Ubuntu, which is closely-related to Debian, but of note its toolchain can be updated with a library of newer compilers. In a Dockerfile you can install the toolchain and build the application.
FROM ubuntu:24.04
# Make the ubuntu-toolchain-r PPA available
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install --assume-yes software-properties-common \
&& add-apt-repository ppa:ubuntu-toolchain-r/ppa
# Install dependencies, including a newer gcc
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install --assume-yes \
build-essential \
gcc-14
# If you need any libraries, also install their -dev packages here
# Make gcc-14 be the system default gcc (within the image)
RUN ln -sf gcc-14 /usr/bin/gcc
# Copy in the application and build it
WORKDIR /app
COPY ./ ./
RUN cmake --build
I'd remove the CMake default preset that forces vcpkg. You can rename it if you'd like, and then cmake --preset vcpkg --build
would build with it.
You'd build this image like any other, but getting the binary out is a little tricky. You need to create a temporary container so that you can run docker cp
.
docker build -t my-app .
docker create --name temp-app my-app
docker cp -r temp-app:/app/out ./out
docker rm temp-app
Note that, if your host is not a Linux host, this will produce a Linux binary, which could be problematic for you. The build environment will also have different C shared libraries installed. This frequently isn't a problem but it occasionally is, especially if there's a newer version of the GNU C library in the newer version of the Linux distribution.
(I generally disagree with the assertion that isolating build tools inside a container simplifies the build process.)
If your actual goal is to run the program in a container too then you should use a Docker multi-stage build for this. The idea is to build the application in an image, and then build a second copy of the image that doesn't include the (usually very large) toolchain. That turns out to be simpler to run, provided your application is amenable to being run in a container (it doesn't depend on local storage or interact on stdio, for example).
You'd update the Dockerfile:
FROM ubuntu:24.04 AS build
# ^^^^^^^^ add
# ...all of the toolchain setup and build from above...
RUN cmake --build
RUN cmake --install
FROM ubuntu:24.04
# Get the installed application from the previous stage
COPY --from=build /usr/local /usr/local
# Set the standard container metadata to run it
CMD ["myapp"]
It's often good practice to create a non-root user in the last stage and switch to it as one of the last steps. If your application needs C shared libraries from Debian packages, you'd also need to RUN apt-get update && apt-get install
the non-development packages in the final stage.
Upvotes: 1