Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CMakeLists.txt and Makefile for clean & fast builds of external projects that use Current. #956

Merged
merged 25 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b08d33c
Preparing a universal `cmake`-based build.
dimacurrentai Jan 2, 2024
906b632
Added the `Makefile` to accompany this `CMakeLists.txt`.
dimacurrentai Jan 2, 2024
28c875d
Added `cmake/run-cmake-test.sh`.
dimacurrentai Jan 2, 2024
baca2bd
Added the `cmake.yml` action.
dimacurrentai Jan 2, 2024
8f1f1ba
Run the `cmake.yml` action on macOS too.
dimacurrentai Jan 2, 2024
ea55bfc
Renamed the action.
dimacurrentai Jan 2, 2024
cd1bcb2
Made it run on each PR commit.
dimacurrentai Jan 2, 2024
d068bac
More Action-friendly output.
dimacurrentai Jan 2, 2024
35d44d5
Added a `TODO`.
dimacurrentai Jan 2, 2024
b0a21ec
Added the test for `lib_*.{cc,h}`.
dimacurrentai Jan 2, 2024
92786db
Added support for `.so` targets as well.
dimacurrentai Jan 2, 2024
53ef2ff
Using single quotes instead of tick quotes in `cat <<EOF` because macOS.
dimacurrentai Jan 2, 2024
e836870
macOS experiments on Github ...
dimacurrentai Jan 2, 2024
c08e357
... more macOS experiments.
dimacurrentai Jan 2, 2024
f1e9391
... and even more ...
dimacurrentai Jan 2, 2024
3141d35
Looks like macOS is defeated, tests pass.
dimacurrentai Jan 2, 2024
faf8a33
Work in progress for cross-platform `dlopen`.
dimacurrentai Jan 2, 2024
2962d0b
Fixed it!
dimacurrentai Jan 2, 2024
d719640
Final `cmake/README.md tweaks.
dimacurrentai Jan 2, 2024
05e56a4
Minor.
dimacurrentai Jan 2, 2024
ab8fb2e
Minor II.
dimacurrentai Jan 2, 2024
6e93509
Removed the `q/q/q` test dir, whoa.
dimacurrentai Jan 3, 2024
57208a4
Minor README tweak.
dimacurrentai Jan 3, 2024
cdbadb1
Made the test run dir truly nested.
dimacurrentai Jan 3, 2024
c52ecc6
The [hopefully] last minor tweak for this PR.
dimacurrentai Jan 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: cmake

on:
workflow_dispatch:
pull_request:
types: [opened, synchronize]

jobs:
try-cmake:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: git clone
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: run test
run: |
export TESTDIR="cmake_test_$(date +%s)"
echo "Running in ${TESTDIR}"
mkdir "${TESTDIR}"
cp cmake/Makefile "${TESTDIR}/"
cp cmake/CMakeLists.txt "${TESTDIR}/"
cp cmake/run-cmake-test.sh "${TESTDIR}/"
(cd "${TESTDIR}"; ./run-cmake-test.sh)
68 changes: 68 additions & 0 deletions cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# TODO(dkorolev): Consider intelligently re-running `cmake` from the `Makefile` if the set of source files has changed.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE(dkorolev), @mzhurovich, this was tested a long time ago in https://github.com/dkorolev/example_use_current_with_cmake, just cleaned up a bit and moved this into current.'


cmake_minimum_required(VERSION 3.14.1)

project(cmake_trivial_2023 C CXX)

set (CMAKE_CXX_STANDARD 17)
find_package(Threads REQUIRED)

# Settings for `googletest`. It builds faste without `gmock`.
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
option(BUILD_GMOCK OFF)

# The helper to clone a dependency, or use it from a sibling dir if available.
function(UseOrGitClone dep remote branch)
if(EXISTS "${CMAKE_SOURCE_DIR}/${dep}" AND IS_DIRECTORY "${CMAKE_SOURCE_DIR}/${dep}")
message(STATUS "Using `${dep}` from `CMAKE_SOURCE_DIR/${dep}'.")
add_subdirectory("${CMAKE_SOURCE_DIR}/${dep}" ${dep})
message(STATUS "Using `${dep}` from `CMAKE_SOURCE_DIR/${dep}': Configured.")
elseif(EXISTS "${CMAKE_SOURCE_DIR}/../${dep}" AND IS_DIRECTORY "${CMAKE_SOURCE_DIR}/../${dep}")
message(STATUS "Using `${dep}` from `CMAKE_SOURCE_DIR/../${dep}'.")
add_subdirectory("${CMAKE_SOURCE_DIR}/../${dep}" ${dep})
message(STATUS "Using `${dep}` from `CMAKE_SOURCE_DIR/../${dep}': Configured.")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../${dep}" AND IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../${dep}")
message(STATUS "Using `${dep}` from `CMAKE_CURRENT_SOURCE_DIR/../${dep}'.")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../${dep}" ${dep})
message(STATUS "Using `${dep}` from `CMAKE_CURRENT_SOURCE_DIR/../${dep}': Configured.")
else()
message(STATUS "Cloning `${dep}` from `${remote}:${branch}` ...")
execute_process(OUTPUT_QUIET ERROR_QUIET COMMAND git clone --depth 1 -b ${branch} ${remote})
file(TOUCH .gitignore)
file(APPEND .gitignore "${dep}/\n")
add_subdirectory("${CMAKE_SOURCE_DIR}/${dep}" ${dep})
message(STATUS "Cloning `${dep}` from `${remote}:${branch}`: Configured.")
endif()
endfunction()

UseOrGitClone(current https://github.com/c5t/current stable)
UseOrGitClone(googletest https://github.com/c5t/googletest v1.14)

# Declare libraries as library targets. And add them into the `ALL_LIBRARIES` list.
set(ALL_LIBRARIES "Threads::Threads" "C5T")
file(GLOB_RECURSE LIBRARY_SOURCE_FILES "src/lib_*.cc")
foreach(LIBRARY_SOURCE_FILE ${LIBRARY_SOURCE_FILES})
get_filename_component(LIBRARY_TARGET_NAME "${LIBRARY_SOURCE_FILE}" NAME_WE)
add_library(${LIBRARY_TARGET_NAME} "${LIBRARY_SOURCE_FILE}")
list(APPEND ALL_LIBRARIES "${LIBRARY_TARGET_NAME}")
endforeach()

# Declare binaries as binary targets. And link them against all the libraries.
file(GLOB_RECURSE BINARY_SOURCE_FILES "src/*.cc")
foreach(BINARY_SOURCE_FILE ${BINARY_SOURCE_FILES})
get_filename_component(BINARY_TARGET_NAME "${BINARY_SOURCE_FILE}" NAME_WE)
if(NOT (BINARY_TARGET_NAME MATCHES "^lib_.*$" OR BINARY_TARGET_NAME MATCHES "^test_.*$"))
add_executable(${BINARY_TARGET_NAME} "${BINARY_SOURCE_FILE}")
target_link_libraries(${BINARY_TARGET_NAME} PRIVATE "${ALL_LIBRARIES}")
endif()
endforeach()

# Declare tests as test targets. And link them against all the libraries.
enable_testing()
file(GLOB_RECURSE TEST_SOURCE_FILES "src/test_*.cc")
foreach(TEST_SOURCE_FILE ${TEST_SOURCE_FILES})
get_filename_component(TEST_TARGET_NAME "${TEST_SOURCE_FILE}" NAME_WE)
add_executable(${TEST_TARGET_NAME} "${TEST_SOURCE_FILE}")
target_link_libraries(${TEST_TARGET_NAME} PRIVATE gtest_main "${ALL_LIBRARIES}")
add_test(NAME ${TEST_TARGET_NAME} COMMAND ${TEST_TARGET_NAME})
endforeach()
60 changes: 60 additions & 0 deletions cmake/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# NOTE(dkorolev):
#
# Yes, I am well aware it is ugly to have a `Makefile` for a `cmake`-built project.
#
# However, there is quite some value.
# Please see the README of https://github.com/dimacurrentai/Current/tree/cmake/cmake for more details.
# TODO(dkorolev): Fix the repo & branch on the line above once merged.
#
# Besides, this `Makefiles` makes `:mak` in Vim work like a charm!

.PHONY: release debug release_dir debug_dir release_test debug_test test fmt clean

DEBUG_BUILD_DIR=$(shell echo "$${DEBUG_BUILD_DIR:-.current_debug}")
RELEASE_BUILD_DIR=$(shell echo "$${RELEASE_BUILD_DIR:-.current}")

OS=$(shell uname)
ifeq ($(OS),Darwin)
CORES=$(shell sysctl -n hw.physicalcpu)
else
CORES=$(shell nproc)
endif

CLANG_FORMAT=$(shell echo "$${CLANG_FORMAT:-clang-format}")

release: release_dir CMakeLists.txt
@MAKEFLAGS=--no-print-directory cmake --build "${RELEASE_BUILD_DIR}" -j ${CORES}

release_dir: ${RELEASE_BUILD_DIR}
@grep "^${RELEASE_BUILD_DIR}/$$" .gitignore >/dev/null || echo "${RELEASE_BUILD_DIR}/" >>.gitignore

${RELEASE_BUILD_DIR}: CMakeLists.txt src
@cmake -DCMAKE_BUILD_TYPE=Release -B "${RELEASE_BUILD_DIR}" .

test: release
@(cd "${RELEASE_BUILD_DIR}"; make test)

debug: debug_dir CMakeLists.txt
@MAKEFLAGS=--no-print-directory cmake --build "${DEBUG_BUILD_DIR}" -j ${CORES}

debug_dir: ${DEBUG_BUILD_DIR}
@grep "^${DEBUG_BUILD_DIR}/$$" .gitignore >/dev/null || echo "${DEBUG_BUILD_DIR}/" >>.gitignore

${DEBUG_BUILD_DIR}: CMakeLists.txt src
@cmake -B "${DEBUG_BUILD_DIR}" .

debug_test: debug
@(cd "${DEBUG_BUILD_DIR}"; make test)

test: release_test

fmt:
${CLANG_FORMAT} -i src/*.cc src/*.h

# TODO(dkorolev): Use the proper repo & branch name.
CMakeLists.txt:
@curl -s https://raw.githubusercontent.com/dimacurrentai/Current/cmake/cmake/CMakeLists.txt >$@
# TODO(dkorolev): Fix the repo & branch on the line above once merged.

clean:
rm -rf "${DEBUG_BUILD_DIR}" "${RELEASE_BUILD_DIR}"
21 changes: 21 additions & 0 deletions cmake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# `C5T/current/cmake/`

This directory contains the `CMakeLists.txt`-based setup for a user project that needs Current.

This `CMakeLists.txt` is accompanied by a `Makefile`. Together, they:

* Make the user depend only on the `Makefile`.
* I.e., it is enough to obtain the `Makefile` from this dir, the rest is magic.
* This `Makefile` will `curl` the `CMakeLists.txt` file as needed.
* Builds the code from `src/`.
* Every `src/*.cc` becomes a binary.
* Every `src/lib*.cc` becomes a library.
* Every `src/test*.cc` becomes a test target.
* All libraries are linked to all binaries and tests for this simple setup.
* Defines many useful `make` targets:
* `clean`, `debug`, `release`, `test`, `release_test`, `clean`, `fmt`, etc.
* Uses `current` and `googletest` from `..` or `../..` if they exist.
* Clones `current` and `googletest` if needed.
* Adds them into `.gitignore` if they are cloned into the local dir.

The `test/` directory in this repo illustrates what it takes to use this `Makefile` + `CMakeLists.txt`.
159 changes: 159 additions & 0 deletions cmake/run-cmake-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/bin/bash
#
# This script illustrates how to use the CMakeLists.txt-based build of project that use `current`.
# It is meant to be run from within an empty directory.

set -e

touch .gitignore
touch golden_gitignore

if ! diff golden_gitignore .gitignore ; then
echo "Diff failed on line ${LINENO}."
exit 1
fi

if ! [ -s Makefile ] ; then
echo 'Need the `Makefile`, `curl`-ing one.'
curl -s https://raw.githubusercontent.com/dimacurrentai/Current/cmake/cmake/Makefile >Makefile
# TODO(dkorolev): Fix the repo & branch on the line above once merged.
fi

mkdir -p src
cat >src/hw.cc <<EOF
#include <iostream>
int main() {
#ifdef NDEBUG
std::cout << "Hello, World! NDEBUG=1." << std::endl;
#else
std::cout << "Hello, World! NDEBUG is unset." << std::endl;
#endif
}
EOF

# This runs `cmake .` for Release mode, which is output into `.current`.
echo "::group::configure"
make .current
echo "::endgroup::"

# The run of `cmake .` must have cloned `current` and `googletest` and added them into `.gitignore`.
if ! [ -d current ] || ! [ -d googletest ] ; then
echo 'Either `current` or `googletest` were not cloned.'
exit 1
fi

echo 'current/' >>golden_gitignore
echo 'googletest/' >>golden_gitignore
if ! diff golden_gitignore .gitignore ; then
echo "Diff failed on line ${LINENO}."
exit 1
fi

echo "::group::make [release]"
make
echo "::endgroup::"

echo '.current/' >>golden_gitignore
if ! diff golden_gitignore .gitignore ; then
echo "Diff failed on line ${LINENO}."
exit 1
fi

echo "::group::.current/hw"
.current/hw
echo "::endgroup::"

echo "::group::make debug"
make debug
echo "::endgroup::"

echo '.current_debug/' >>golden_gitignore
diff golden_gitignore .gitignore || (echo 'Wrong `.gitignore`, exiting.'; exit 1)

echo "::group::.current_debug/hw"
.current_debug/hw
echo "::endgroup::"

cat >src/lib_add.cc <<EOF
#include "lib_add.h"
int lib_add(int a, int b) {
return a + b;
}
EOF

cat >src/lib_add.h <<EOF
#pragma once
int lib_add(int a, int b);
EOF

cat >src/test_gtest.cc <<EOF
#include <gtest/gtest.h> // IWYU pragma: keep
#include "lib_add.h"
TEST(SmokeGoogletest, TwoTimesTwo) {
EXPECT_EQ(4, 2 * 2);
}
TEST(SmokeGoogletest, TwoPlusThree) {
EXPECT_EQ(5, lib_add(2, 3));
}
EOF

cat >src/test_current_gtest.cc <<EOF
#include "3rdparty/gtest/gtest-main.h" // IWYU pragma: keep
#include "lib_add.h"
TEST(SmokeCurrentGoogletest, TwoTimesTwo) {
EXPECT_EQ(4, 2 * 2);
}
TEST(SmokeCurrentGoogletest, TwoPlusThree) {
EXPECT_EQ(5, lib_add(2, 3));
}
EOF

echo "::group::release_test"
make test
echo "::endgroup::"

echo "::group::debug_test"
make debug_test
echo "::endgroup::"

touch src/test_gtest.cc
T0_GTEST=$(date +%s)
echo "::group::one line change google gtest release"
make test
echo "::endgroup::"
T1_GTEST=$(date +%s)

touch src/test_current_gtest.cc
T0_CURRENT_GTEST=$(date +%s)
echo "::group::one line change current gtest release"
make test
echo "::endgroup::"
T1_CURRENT_GTEST=$(date +%s)

touch src/test_gtest.cc
T0_DEBUG_GTEST=$(date +%s)
echo "::group::one line change google gtest debug"
make debug_test
echo "::endgroup::"
T1_DEBUG_GTEST=$(date +%s)

touch src/test_current_gtest.cc
T0_DEBUG_CURRENT_GTEST=$(date +%s)
echo "::group::one line change current gtest debug"
make debug_test
echo "::endgroup::"
T1_DEBUG_CURRENT_GTEST=$(date +%s)

echo "One-line change time, Current gtest, debug: $((T1_DEBUG_CURRENT_GTEST - T0_DEBUG_CURRENT_GTEST))s"
echo "One-line change time, Current gtest, release: $((T1_CURRENT_GTEST - T0_CURRENT_GTEST))s"
echo
echo "One-line change time, Google gtest, debug: $((T1_DEBUG_GTEST - T0_DEBUG_GTEST))s"
echo "One-line change time, Google gtest, release: $((T1_GTEST - T0_GTEST))s"
echo
echo '(The numbers for `Current gtest` should be worse, as Current is header-only.)'

for i in ./.current/test_gtest ./.current/test_current_gtest ./.current_debug/test_gtest ./.current_debug/test_current_gtest ; do
echo "::group::test output for $i"
$i
echo "::endgroup::"
done