Container Development With Visual Studio Code Extensions, Docker, and SourcePro
October 18, 2019

C++ Container Development With Visual Studio Code Extensions, Docker, and SourcePro

DevOps

Container-based development is rising in popularity in software development environments. However, despite their flexibility, using containers in C++ development can create its own set of unique challenges. A big one is the lack of a native editor due to the fact that containers typically do not have a display.

With the new Visual Studio Code Extension called Remote - Containers, Microsoft has aimed to alleviate this problem. Remote - Containers allow a developer to use Visual Studio Code as if it were running inside the container. This creates an environment with easier project management, development, and debugging for developers.

In this article, we'll look at how you can take advantage of a container development environment when working with SourcePro, the Visual Studio Code Extension, and Docker.

While each piece of the example below will be explained, if you're new to Docker, Containers, or even Visual Studio Code, it may help to get a better understanding of them before proceeding.

How to Set Up C++ Container Development

Using SourcePro, Visual Studio Code, and Docker

The example we'll be looking at is an application relying on SourcePro Core and the internationalization module. In this way, we can see how third party libraries might be incorporated. We break the example down into the three main components:

  • Docker configuration.
  • Project files.
  • Visual Studio Code configuration.

0. Review Requirements and Instructions

Requirements

Instructions

  1. For first time users, follow Microsoft's getting started instructions.
  2. Clone the example repository
  3. Set up SourcePro:
    • Download sourcepro_2018.2_linux_x86-64.run to the .devcontainer folder.
    • Download the appropriate SourcePro license.key file and place it into the .devcontainer folder.
  4. Open the project folder in Visual Studio Code.
  5. Press F1 (or press the green button in the bottom left-hand corner) and select the "Remote-Containers: Reopen Folder in Container..." command.
  6. (Optional) Run the example application:
    1. Press F5 to build and run.
    2. The example application should output "hello world" which can be viewed by selecting the TERMINAL tab in Visual Studio Code.

 

1. Configure Docker

At the project's root directory the .devcontainer folder allows us to specify how to set up our Docker container along with how Visual Studio Code should use it.

The first piece we'll look at is the devcontainer.json file.

 

{
	"name": "SourcePro Container Example",
	"dockerFile": "Dockerfile",
	"extensions": [
		"ms-vscode.cpptools"
	],
	"runArgs": [
		"--cap-add=SYS_PTRACE",
		"--security-opt",
		"seccomp=unconfined"
	]
}

 

FROM centos:7

# General yum update.
RUN yum -y update

# Install general purpose development tools and libraries
RUN yum -y groupinstall "Development Tools" "Development Libraries"
RUN yum -y install cppcheck valgrind wget bzip2

# Update to Git 2.x
RUN yum -y remove git
RUN yum -y install  https://centos7.iuscommunity.org/ius-release.rpm
RUN yum -y install  git2u-all

# Install SourcePro
COPY sourcepro_2018.2_linux_x86-64.run .
COPY license.key .
RUN chmod +x sourcepro_2018.2_linux_x86-64.run
RUN ./sourcepro_2018.2_linux_x86-64.run --mode unattended --license-file license.key
RUN rm license.key sourcepro_2018.2_linux_x86-64.run

# Build SourcePro
COPY buildspec.bsf .
RUN /root/RogueWave/SourcePro/2018.2/rcb/bin/rcb build -b buildspec.bsf \
    -D common.buildspace=/root/RogueWave/SourcePro/2018.2

# Move and clean up SourcePro install.
ENV SOURCEPRO /opt/SourcePro
RUN mkdir -p ${SOURCEPRO}/3rdparty
RUN mv /root/RogueWave/SourcePro/2018.2/3rdparty/icu-58.2 ${SOURCEPRO}/3rdparty
RUN cd /root/RogueWave/SourcePro/2018.2/ && mv lib rw ${SOURCEPRO}
RUN rm -rf /root/RogueWave/

# Add ICU 58 to library path.
ENV LD_LIBRARY_PATH ${SOURCEPRO}/3rdparty/icu-58.2/linux/em64t/lib:${LD_LIBRARY_PATH}

# Clean-up yum.
RUN yum -y autoremove

# Use bash instead of sh.
ENV SHELL /bin/bash

 

Looking at each piece of our example Dockerfile, we can get a better understanding of how it works.With this, we've informed Visual Studio Code to preinstall Microsoft's C++ extension into the container and add the appropriate run arguments to ensure debugging capability. More importantly we've told Visual Studio Code we'll be using a Dockerfile to integrate with Docker.

Placed in the same directory, the Dockerfile defines the steps required to build our development container's image.

 

FROM centos:7

 

In order to stay within SourcePro's supported platforms, we're using the CentOS 7 image provided by Docker.

 

# General yum update.
RUN yum -y update

# Install general purpose development tools and libraries
RUN yum -y groupinstall "Development Tools" "Development Libraries"
RUN yum -y install cppcheck valgrind wget bzip2

# Update to Git 2.x
RUN yum -y remove git
RUN yum -y install  https://centos7.iuscommunity.org/ius-release.rpm
RUN yum -y install  git2u-all

 

# Install SourcePro
COPY sourcepro_2018.2_linux_x86-64.run .
COPY license.key .
RUN chmod +x sourcepro_2018.2_linux_x86-64.run
RUN ./sourcepro_2018.2_linux_x86-64.run --mode unattended --license-file license.key
RUN rm license.key sourcepro_2018.2_linux_x86-64.run

 

Basic build tools are installed into the system. Additionally, Git 2.x is installed to ensure we have access to all of the Visual Studio Code's Git integration.

Both the installer and license key are copied into the container from the .devcontainer directory, and the installer is run.

In this example the default install location is used: /root/RogueWave/SourcePro/2018.2/

 

# Build SourcePro
COPY buildspec.bsf .
RUN /root/RogueWave/SourcePro/2018.2/rcb/bin/rcb build -b buildspec.bsf \
    -D common.buildspace=/root/RogueWave/SourcePro/2018.2

 

Our build specification file (buildspec.bsf) is copied into the image and rcb is invoked using the SourcePro install directory as the build space.

 

# Move and clean up SourcePro install.
ENV SOURCEPRO /opt/SourcePro
RUN mkdir -p ${SOURCEPRO}/3rdparty
RUN mv /root/RogueWave/SourcePro/2018.2/3rdparty/icu-58.2 ${SOURCEPRO}/3rdparty
RUN cd /root/RogueWave/SourcePro/2018.2/ && mv lib rw ${SOURCEPRO}
RUN rm -rf /root/RogueWave/

 

In order to keep our container image size to a minimum, we move SourcePro's build to a new location and clean up any unneeded build tools.

 

# Add ICU 58 to library path.
ENV LD_LIBRARY_PATH ${SOURCEPRO}/3rdparty/icu-58.2/linux/em64t/lib:${LD_LIBRARY_PATH}

 

ICU 58's library is added to LD_LIBRARY_PATH to allow for dynamic linkage.

 

# Clean-up yum.
RUN yum -y autoremove

# Use bash instead of sh.
ENV SHELL /bin/bash

 

Finally we clean up yum and switch the default shell to bash.

With all of this complete, when our newly created Docker container is used, it will provide a valid environment for developing against SourcePro.

2. Create Project Files

Now that a well-defined container has been established, we need to create the project we would like to build. Any files placed into our project folder will automatically be mounted into the container, so we can treat this as if the host system did not exist. To keep things simple, we'll look at a makefile and single C++ source file.

 

# General make variables
OBJS = main.o
SOURCE = main.cpp
OUT = main.out
CC = g++

# SourcePro Flags
# This example relies on the Internationalization module of SourcePro.
# For more information, please visit the SourcePro build guide.
# https://docs.roguewave.com/en/sourcepro/current/HTML/index.html#page/Rogue%2520Wave%2520Component%2520Builder%2520(RCB)%2Frcbbd-i18n.16.1.html%23
ICU_HOME = ${SOURCEPRO}/3rdparty/icu-58.2/linux/em64t
SOURCEPRO_FLAGS = -I${SOURCEPRO} -I$(ICU_HOME)/include -D_RWCONFIG=15s
SOURCEPRO_LFLAGS = \
	-L${SOURCEPRO}/lib -L$(ICU_HOME)/lib \
	-li18n15s -licudata -licui18n -licuuc \
	-lthread15s -litc15s -lfunctor_list15s \
	-lfunctor15s -lpointer15s -lsync15s \
	-lthrexcept15s -ltrace15s -ltls15s -pthread

# Compose final flag variables
FLAGS = -g -c -Wall $(SOURCEPRO_FLAGS)
LFLAGS = $(SOURCEPRO_LFLAGS)

all: $(OBJS)
	$(CC) -g $(OBJS) -o $(OUT) $(LFLAGS)

main.o: main.cpp
	$(CC) $(FLAGS) main.cpp -std=c++11

clean:
	rm -f $(OBJS) $(OUT)

 

Utilizing the SOURCEPRO environment variable defined in Dockerfile we can construct a makefile to build our application.

 

#include <rw/i18n/RWUFromUnicodeConverter.h>
#include <rw/i18n/RWUInit.h>
#include <rw/i18n/RWUString.h>
#include <rw/i18n/RWURegexMatchIterator.h>
#include <rw/i18n/RWURegularExpression.h>
#include <rw/cstring.h>
#include <iostream>

int main()
{
    RWUInit init;
    RWCString pattern("[{C}_]+");
    RWURegularExpression exp(pattern, RWURegularExpression::Basic, RWURegularExpression::IgnoreCase);

    RWUString text("\\ud835\\udcf1\\ud835\\udcee\\ud835\\udcf5\\ud835\\udcf5\\ud835\\udcf8, \\ud835\\udd00\\ud835\\udcf8\\ud835\\udcfb\\ud835\\udcf5\\ud835\\udced");
    text.unescape();

    RWURegexMatchIterator endIter;
    for (RWURegexMatchIterator iter(exp, text); iter != endIter; ++iter) {
        std::cout << iter->subString(text, 0) << std::endl;
    }

    return 0;
}

 

Here we've crafted a simple C++ file to verify everything is compiling and linking correctly.

3. Configure Visual Studio Code and Visual Studio Code Extension 

At this point, our container and application are ready to go. However, in order to make full use of Visual Studio Code's C++ integration, it's a good idea to define some behaviors. We can do this through the .vscode directory.

 

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build Main",
            "type": "shell",
            "command": "make",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

 

In tasks.json we can specify custom tasks that Visual Studio Code should be aware of. For now, we only need to define what we'll be using the make command to build the example.

 

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Main",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/main.out",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build Main"
        }
    ]
}

 

launch.json allows us to define how Visual Studio Code should run our application. In this case, we also specify the attachment of GDB, allowing integration into Visual Studio Code's debugging tools. Note the preLaunchTask. This is how we reference our build task defined in tasks.json.

4. Go Deeper

While the example provided here will be enough to get started, this only scratches the surface of what's possible.

  • Container access is not limited to the use of a Dockerfile. Additional options can be found in Microsoft's documentation.
  • Visual Studio Code supports many debugging capabilities, including the use of breakpoints, thread and call-stack inspection, and variable watching. Check out Microsoft's Developing inside a Container.
  • All files inside the container are accessible via Visual Studio Code's inspection tools, including SourcePro's header files.

Try SourcePro Today For C++ Container Development

SourcePro C++ class libraries provide the fundamental building blocks for your applications. With SourcePro, you write your code once and deploy it on any platform. This helps you reduce time-to-market, increase reliability, and extend the life of your applications.

You can get started with SourcePro today for better C++ container development. Request an evaluation of SourcePro to get started.  

Request an Evaluation of SourcePro