I've been itching to kick the tires on the CLion IDE to see how it works as a contemporary C and C++ development environment. I've been using Jetbrain's IntelliJ IDE ever since my student,Richard Robinson, recommended it to me for Java development. I typically write C programs using embedded IDEs like MCUXpresso, Segger Embedded Studio and MPLAB X so CLion feels like a hybrid between those IDEs and IntelliJ. So, while I have wanted to try out CLion, I've had too many other projects on the go with embedded targets or regular Java programming to give it a serious go.
That changed with a need to get a serial port program running in C on macOS. As part of the work that I'm doing with Mohaimen Hassan as part of the Designing Sound Futures research project we ran across SigRok's cross-platform libserialport library. Just like we are using the TinyUSB library to control serial communication on the embedded device side I would like to be able to use something similar on the host or edge device side -- hence libserialport.
This blog post is based on the work that I did to get an example programming working in CLion on a Mac. I'm going to assume that the process is very similar on a Linux box and I really haven't tried this out at all on Windows.
Step 1: import the library
Importing libraries for use in C is not nearly as straightforward as it is with Java and tools like Maven. The method for import depends on your operating system:
- macOS: use homebrew.
- Linux: use apt, pacman, or the equivalent for your distribution
- Windows: I'm not sure. (possibility? or this?)
(Optional) Step 2: Command-line compiling & testing out CMake
I sometimes get lost in all of the options and drop-down menus and sub-windows in an IDE like CLion. So, sometimes, I find it best to get my bearings by trying to compile a program from the command line directly. In fact, I had a lot of trouble getting a simple program to compile right with CLion rdue to linker issues. I believe that the source of the issue was the that I couldn't get CLion to find the object files that had been installed by HomeBrew.
The solution was to download the libserialport GitHub project's files and to run through the instructions found in the README file, specifically:
Run "./autogen.sh" to generate the build system, "./configure" to setup, then
from the libserialport README
"make" to build the library and "make install" to install it.
I had to use homebrew to install a number of packages for this to work:
- brew install libserialport
- brew install autoconf
- brew install automake
- brew install libtool
- brew install pkg-config
- brew install cmake
Depending on the state of your development tools you may have to use apt, pacman or homebrew to get the right mix.
After you've "made" the main library files enter the examples directory and type
- make clean
- make all
and you should see something like the following:
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include await_events.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o await_events
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include handle_errors.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o handle_errors
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include list_ports.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o list_ports
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include port_config.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o port_config
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include port_info.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o port_info
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include send_receive.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o send_receive
The Makefile is responsible for making all the gcc compiler calls and reaches out to the Homebrew-installed library in /opt/homebrew/Cellar/libserialport/0.1.1 using the -I and the -L flags to explicitly tell gcc where the linkable object and header files are, respectively.
Knowing that CLion likes to use CMake to generate its makefiles, but not being familiar with CMake, I reached out to Bing (ChatGPT) and asked it to use this line:
gcc -g -Wall -I/opt/homebrew/Cellar/libserialport/0.1.1/include list_ports.c -L/opt/homebrew/Cellar/libserialport/0.1.1/lib -lserialport -o list_ports
As the model for a CMakeLists.txt file. I took what Bing/ChatGPT spat out and adjusted it somewhat :
CMakeLists.txt for command-line compiling exercise
cmake_minimum_required(VERSION 3.0)
project(list_ports)
#Set compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall")
#Include directories
include_directories(/opt/homebrew/Cellar/libserialport/0.1.1/include)
#Link directories
link_directories(/opt/homebrew/Cellar/libserialport/0.1.1/lib)
#Create an executable target
add_executable(list_ports list_ports.c)
#Link against libserialport
target_link_libraries(list_ports serialport)
Then, in the examples directory I make a build subdirectory:
- mkdir build
- then moved into it:
- cd build
- then ran cmake:
- cmake ..
- cmake --build .
and then I could execute the list_ports file that was produced.
Armed with that, I could move on to creating a CLion project
Step 3: Create a CLion Project
I chose to make a C project within CLion. The wizard created a main.c file with the standard "hello world" print statement. I replaced the whole thing with the contents from the libserialport's list_ports.c file. Then, I modified the CMakeLists.txt file listed above within the pre-fab'd file that's found in the CLion project files, as follows:
cmake_minimum_required(VERSION 3.28)
set(CMAKE_C_STANDARD 23)
set(PROJECT_NAME temp_libserialport_v3b)
set(SERIAL_LIBRARY_DIRECTORY /opt/homebrew/Cellar/libserialport/0.1.1/)
project(${PROJECT_NAME} C)
# Set compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall")
# Include directories
include_directories(${SERIAL_LIBRARY_DIRECTORY}/include)
# Link directories
link_directories(${SERIAL_LIBRARY_DIRECTORY}/lib)
# Create an executable target
add_executable(${PROJECT_NAME} main.c)
# Link against libserialport
target_link_libraries(${PROJECT_NAME} serialport)
In my previous attempts at getting this to work I didn't have a target_link_libraries setting. The include and lib settings were pretty obvious to me, but the target_link_library wasn't.
I parametrized things a little bit in the hopes that I can reuse this for other project, and I've given this project a really silly name, temp_libserialport_v3b, but that's easily changed. I made sure to click on the CMake triangle on the left side and to hit the refresh icon within it. Then, I entered the main.c file and ran it using the green triangle. The result? It looked for and identified serial ports on my computer. Success!
Conclusion
The libserialport library looks really interesting and useful, but setting up the CMake file is a little tricky. Hopefully the hints here are helpful for others going down a similar path.
James Andrew Smith is a Professional Engineer and Associate Professor in the Electrical Engineering and Computer Science Department of York University's Lassonde School, with degrees in Electrical and Mechanical Engineering from the University of Alberta and McGill University. Previously a program director in biomedical engineering, his research background spans robotics, locomotion, human birth and engineering education. While on sabbatical in 2018-19 with his wife and kids he lived in Strasbourg, France and he taught at the INSA Strasbourg and Hochschule Karlsruhe and wrote about his personal and professional perspectives. James is a proponent of using social media to advocate for justice, equity, diversity and inclusion as well as evidence-based applications of research in the public sphere. You can find him on Twitter. Originally from Québec City, he now lives in Toronto, Canada.
Updates
Aug 1, 2024: typo fixed and DSF link added. https://www.designingsoundfutures.org.