diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..81caa95 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: ci + +on: + push: + paths: + - "**.c" + - "**.cmake" + - "**/CMakeLists.txt" + - ".github/workflows/ci.yml" + workflow_dispatch: + +# avoid wasted runs +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + + unix: + + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + + steps: + - &checkout + uses: actions/checkout@v4 + + - run: cmake --workflow default + + windows_msvc: + runs-on: windows-latest + timeout-minutes: 5 + + steps: + - *checkout + + - run: cmake --workflow release diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cd1b4b..07a181d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,31 +1,64 @@ -cmake_minimum_required(VERSION 2.8) -project(GKlib C) +cmake_minimum_required(VERSION 3.16...4.1) -option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) +if(NOT CMAKE_BUILD_TYPE AND NOT DEFINED ENV{CMAKE_BUILD_TYPE}) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type") +endif() + +project(GKlib LANGUAGES C +VERSION 1.0.0 +) + +enable_testing() + +include(GNUInstallDirs) + +option(BUILD_TESTING "Build tests") + +option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)") + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND GKlib_IS_TOP_LEVEL) + set(CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}/local" CACHE PATH "install prefix" FORCE) +endif() + +set(CMAKE_C_STANDARD 99) + +message(STATUS "${PROJECT_NAME} ${PROJECT_VERSION} CMake ${CMAKE_VERSION} Arch: ${CMAKE_SYSTEM_PROCESSOR} install prefix: ${CMAKE_INSTALL_PREFIX}") + +set(GKLIB_PATH ${PROJECT_SOURCE_DIR}) -get_filename_component(abs "." ABSOLUTE) -set(GKLIB_PATH ${abs}) -unset(abs) include(GKlibSystem.cmake) -include_directories(".") -if(MSVC) - include_directories("win32") - file(GLOB win32_sources RELATIVE "win32" "*.c") -else(MSVC) - set(win32_sources, "") -endif(MSVC) +set(win32_inc $<$:${PROJECT_SOURCE_DIR}/win32>) +set(win32_sources $<$:win32/adapt.c>) + +set(GKlib_sources b64.c evaluate.c gkregex.c mcore.c seq.c +blas.c fkvkselect.c graph.c memory.c sort.c +cache.c fs.c htable.c pqueue.c string.c +csr.c getopt.c io.c random.c timers.c +error.c gk_util.c itemsets.c rw.c tokenizer.c) add_library(GKlib ${GKlib_sources} ${win32_sources}) +target_include_directories(GKlib PUBLIC +$ +$ +) +target_link_libraries(GKlib PRIVATE +$<$:m> +$<$:OpenMP::OpenMP_C> +) + +add_library(GKlib::GKlib INTERFACE IMPORTED GLOBAL) +target_link_libraries(GKlib::GKlib INTERFACE GKlib) + +if(BUILD_TESTING) +add_subdirectory(test) +endif() -if(UNIX) - target_link_libraries(GKlib m) -endif(UNIX) +install(TARGETS GKlib EXPORT ${PROJECT_NAME}-targets) -include_directories("test") -add_subdirectory("test") +install(FILES GKlib.h TYPE INCLUDE) +# must have trailing slash or it makes a vestigial subdir +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ TYPE INCLUDE +FILES_MATCHING PATTERN "gk_*.h") -install(TARGETS GKlib - ARCHIVE DESTINATION lib/${LINSTALL_PATH} - LIBRARY DESTINATION lib/${LINSTALL_PATH}) -install(FILES ${GKlib_includes} DESTINATION include/${HINSTALL_PATH}) +include(cmake/install.cmake) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..eac26dd --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,80 @@ +{ + "version": 6, + +"configurePresets": [ +{ + "name": "default", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "BUILD_TESTING": true + } +} +], +"buildPresets": [ + { + "name": "default", + "configurePreset": "default" + }, + { + "name": "release", + "configurePreset": "default", + "configuration": "Release" + } +], +"testPresets": [ +{ + "name": "default", + "configurePreset": "default", + "output": { + "outputOnFailure": true, + "verbosity": "verbose" + }, + "execution": { + "noTestsAction": "error", + "scheduleRandom": true, + "stopOnFailure": false, + "timeout": 60 + } +}, +{ + "name": "release", "inherits": "default", + "configuration": "Release" +} +], +"workflowPresets": [ + { + "name": "default", + "steps": [ + { + "type": "configure", + "name": "default" + }, + { + "type": "build", + "name": "default" + }, + { + "type": "test", + "name": "default" + } + ] + }, + { + "name": "release", + "steps": [ + { + "type": "configure", + "name": "default" + }, + { + "type": "build", + "name": "release" + }, + { + "type": "test", + "name": "release" + } + ] + } +] +} diff --git a/GKlibSystem.cmake b/GKlibSystem.cmake index 31a1cf1..d652efb 100644 --- a/GKlibSystem.cmake +++ b/GKlibSystem.cmake @@ -3,150 +3,90 @@ include(CheckFunctionExists) include(CheckIncludeFile) # Setup options. -option(GDB "enable use of GDB" OFF) -option(ASSERT "turn asserts on" OFF) -option(ASSERT2 "additional assertions" OFF) -option(DEBUG "add debugging support" OFF) -option(GPROF "add gprof support" OFF) -option(VALGRIND "add valgrind support" OFF) option(OPENMP "enable OpenMP support" OFF) option(PCRE "enable PCRE support" OFF) option(GKREGEX "enable GKREGEX support" OFF) option(GKRAND "enable GKRAND support" OFF) -option(NO_X86 "enable NO_X86 support" OFF) # Add compiler flags. if(MSVC) - set(GKlib_COPTS "/Ox") - set(GKlib_COPTIONS "-DWIN32 -DMSC -D_CRT_SECURE_NO_DEPRECATE -DUSE_GKREGEX") -elseif(MINGW) - set(GKlib_COPTS "-DUSE_GKREGEX") + set(GKlib_COPTIONS WIN32 MSC _CRT_SECURE_NO_DEPRECATE USE_GKREGEX) +elseif(WIN32) + set(GKlib_COPTIONS USE_GKREGEX) else() - set(GKlib_COPTIONS "-DLINUX -D_FILE_OFFSET_BITS=64") + set(GKlib_COPTIONS LINUX FILE_OFFSET_BITS=64) endif(MSVC) + if(CYGWIN) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DCYGWIN") -endif(CYGWIN) -if(CMAKE_COMPILER_IS_GNUCC) -# GCC opts. - set(GKlib_COPTIONS "${GKlib_COPTIONS} -std=c99 -fno-strict-aliasing") -if(VALGRIND) - set(GKlib_COPTIONS "${GK_COPTIONS} -march=x86-64 -mtune=generic") -else() -# -march=native is not a valid flag on PPC: -if(CMAKE_SYSTEM_PROCESSOR MATCHES "power|ppc|powerpc|ppc64|powerpc64" OR (APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc|ppc64")) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -mtune=native") -else() - set(GKlib_COPTIONS "${GKlib_COPTIONS} -march=native") + list(APPEND GKlib_COPTIONS CYGWIN) +endif() + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + list(APPEND GKlib_COPTS -fno-strict-aliasing -Wall -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unknown-pragmas -Wno-unused-label) endif() -endif(VALGRIND) - if(NOT MINGW) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -fPIC") - endif(NOT MINGW) -# GCC warnings. - set(GKlib_COPTIONS "${GKlib_COPTIONS} -Werror -Wall -pedantic -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unknown-pragmas -Wno-unused-label") -elseif(${CMAKE_C_COMPILER_ID} MATCHES "Sun") -# Sun insists on -xc99. - set(GKlib_COPTIONS "${GKlib_COPTIONS} -xc99") -endif(CMAKE_COMPILER_IS_GNUCC) - -# Intel compiler -if(${CMAKE_C_COMPILER_ID} MATCHES "Intel") - set(GKlib_COPTIONS "${GKlib_COPTIONS} -xHost -std=c99") + +if(UNIX) +include(CheckPIESupported) +check_pie_supported() +set(CMAKE_POSITION_INDEPENDENT_CODE true) endif() # Find OpenMP if it is requested. if(OPENMP) - include(FindOpenMP) - if(OPENMP_FOUND) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -D__OPENMP__ ${OpenMP_C_FLAGS}") - else() - message(WARNING "OpenMP was requested but support was not found") - endif(OPENMP_FOUND) -endif(OPENMP) - -# Set the CPU type -if(NO_X86) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DNO_X86=${NO_X86}") -endif(NO_X86) - -# Add various definitions. -if(GDB) - set(GKlib_COPTS "${GKlib_COPTS} -g") - set(GKlib_COPTIONS "${GKlib_COPTIONS} -Werror") -else() - set(GKlib_COPTS "-O3") -endif(GDB) - - -if(DEBUG) - set(GKlib_COPTS "-g") - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DDEBUG") -endif(DEBUG) - -if(GPROF) - set(GKlib_COPTS "-pg") -endif(GPROF) - -if(NOT ASSERT) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DNDEBUG") -endif(NOT ASSERT) - -if(NOT ASSERT2) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DNDEBUG2") -endif(NOT ASSERT2) + find_package(OpenMP REQUIRED) + list(APPEND GKlib_COPTIONS __OPENMP__) +endif() +# Set the CPU type +if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + list(APPEND GKlib_COPTIONS NO_X86=1) +endif() # Add various options if(PCRE) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -D__WITHPCRE__") -endif(PCRE) + list(APPEND GKlib_COPTIONS __WITHPCRE__) +endif() if(GKREGEX) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DUSE_GKREGEX") -endif(GKREGEX) +list(APPEND GKlib_COPTIONS USE_GKREGEX) +endif() if(GKRAND) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DUSE_GKRAND") -endif(GKRAND) +list(APPEND GKlib_COPTIONS USE_GKRAND) +endif() # Check for features. check_include_file(execinfo.h HAVE_EXECINFO_H) if(HAVE_EXECINFO_H) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DHAVE_EXECINFO_H") + list(APPEND GKlib_COPTIONS HAVE_EXECINFO_H) endif(HAVE_EXECINFO_H) check_function_exists(getline HAVE_GETLINE) if(HAVE_GETLINE) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -DHAVE_GETLINE") + list(APPEND GKlib_COPTIONS HAVE_GETLINE) endif(HAVE_GETLINE) # Custom check for TLS. if(MSVC) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -D__thread=__declspec(thread)") - - # This if checks if that value is cached or not. - if("${HAVE_THREADLOCALSTORAGE}" MATCHES "^${HAVE_THREADLOCALSTORAGE}$") + if(NOT DEFINED HAVE_THREADLOCALSTORAGE) + message(CHECK_START "checking for thread-local storage") try_compile(HAVE_THREADLOCALSTORAGE ${CMAKE_BINARY_DIR} ${GKLIB_PATH}/conf/check_thread_storage.c) if(HAVE_THREADLOCALSTORAGE) - message(STATUS "checking for thread-local storage - found") + message(CHECK_PASS "found") else() - message(STATUS "checking for thread-local storage - not found") + message(CHECK_FAIL "not found") endif() endif() if(NOT HAVE_THREADLOCALSTORAGE) - set(GKlib_COPTIONS "${GKlib_COPTIONS} -D__thread=") + list(APPEND GKlib_COPTIONS __thread=) endif() endif() # Finally set the official C flags. -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GKlib_COPTIONS} ${GKlib_COPTS}") - -# Find GKlib sources. -file(GLOB GKlib_sources ${GKLIB_PATH}/*.c) -file(GLOB GKlib_includes ${GKLIB_PATH}/*.h) +add_compile_options("$<$:${GKlib_COPTS}>") +add_compile_definitions("$<$:${GKlib_COPTIONS}>") diff --git a/LICENSE.txt b/LICENSE similarity index 80% rename from LICENSE.txt rename to LICENSE index b61ca6f..e8d9d95 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -12,7 +12,6 @@ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -implied. See the License for the specific language governing +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.md b/README.md index f94eeea..d1bb0a0 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,11 @@ # GKlib -A library of various helper routines and frameworks used by many of the lab's software -## Build requirements - - CMake 2.8, found at http://www.cmake.org/, as well as GNU make. - -Assuming that the above are available, two commands should suffice to -build the software: -``` -make config -make -``` - -## Configuring the build -It is primarily configured by passing options to make config. For example: -``` -make config cc=icc -``` - -would configure it to be built using icc. - -Configuration options are: -``` -cc=[compiler] - The C compiler to use [default: gcc] -prefix=[PATH] - Set the installation prefix [default: ~/local] -openmp=set - To build a version with OpenMP support -``` +[![ci](https://github.com/scivision/GKlib/actions/workflows/ci.yml/badge.svg)](https://github.com/scivision/GKlib/actions/workflows/ci.yml) +A library of various helper routines and frameworks used by many of the lab's software. -## Building and installing -To build and install, run the following -``` -make -make install -``` +This has been enhanced to work on modern computers with modern CMake. -By default, the library file, header file, and binaries will be installed in +```sh +cmake --workflow --preset default ``` -~/local/lib -~/local/include -~/local/bin -``` - -## Other make commands - make uninstall - Removes all files installed by 'make install'. - - make clean - Removes all object files but retains the configuration options. - - make distclean - Performs clean and completely removes the build directory. - - diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 0000000..cfd7570 --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) + +check_required_components(@PROJECT_NAME@) diff --git a/cmake/install.cmake b/cmake/install.cmake new file mode 100644 index 0000000..b4f3197 --- /dev/null +++ b/cmake/install.cmake @@ -0,0 +1,50 @@ +# --- BOILERPLATE: install / packaging + +include(CMakePackageConfigHelpers) + +configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/config.cmake.in +${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake +INSTALL_DESTINATION cmake +) + +write_basic_package_version_file( +${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake +COMPATIBILITY SameMajorVersion +) + +install(EXPORT ${PROJECT_NAME}-targets +NAMESPACE ${PROJECT_NAME}:: +DESTINATION cmake +) + +install(FILES +${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake +${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake +DESTINATION cmake +) + +# # allow use of package from build directory without installing +export(EXPORT ${PROJECT_NAME}-targets +FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}-targets.cmake +NAMESPACE ${PROJECT_NAME}:: +) + +# --- CPack + +set(CPACK_GENERATOR "TBZ2") +set(CPACK_SOURCE_GENERATOR "TBZ2") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/package) + +# not .gitignore as its regex syntax is more advanced than CMake +set(CPACK_SOURCE_IGNORE_FILES .git/ .github/ .vscode/ _CPack_Packages/ +${CMAKE_BINARY_DIR}/ ${PROJECT_BINARY_DIR}/ +archive/ concepts/ +) + +install(FILES ${CPACK_RESOURCE_FILE_README} ${CPACK_RESOURCE_FILE_LICENSE} +DESTINATION share/docs/${PROJECT_NAME} +) + +include(CPack) diff --git a/io.c b/io.c index 289b401..508c4a4 100644 --- a/io.c +++ b/io.c @@ -16,6 +16,10 @@ This file contains various functions that perform I/O. #undef _GNU_SOURCE #endif +#ifdef _MSC_VER +#include +#endif + #include /************************************************************************* @@ -48,7 +52,7 @@ void gk_fclose(FILE *fp) /*************************************************************************/ -/*! This function is a wrapper around the read() function that ensures +/*! This function is a wrapper around the read() function that ensures that all data is been read, by issuing multiple read requests. The only time when not 'count' items are read is when the EOF has been reached. @@ -71,7 +75,7 @@ ssize_t gk_read(int fd, void *vbuf, size_t count) /*************************************************************************/ -/*! This function is a wrapper around the write() function that ensures +/*! This function is a wrapper around the write() function that ensures that all data is been written, by issueing multiple write requests. */ /*************************************************************************/ @@ -94,7 +98,7 @@ ssize_t gk_write(int fd, void *vbuf, size_t count) /*************************************************************************/ /*! This function is the GKlib implementation of glibc's getline() function. - \returns -1 if the EOF has been reached, otherwise it returns the + \returns -1 if the EOF has been reached, otherwise it returns the number of bytes read. */ /*************************************************************************/ @@ -107,7 +111,7 @@ ssize_t gk_getline(char **lineptr, size_t *n, FILE *stream) int ch; if (feof(stream)) - return -1; + return -1; /* Initial memory allocation if *lineptr is NULL */ if (*lineptr == NULL || *n == 0) { @@ -121,11 +125,11 @@ ssize_t gk_getline(char **lineptr, size_t *n, FILE *stream) (*lineptr)[i++] = (char)ch; /* reallocate memory if reached at the end of the buffer. The +1 is for '\0' */ - if (i+1 == *n) { + if (i+1 == *n) { *n = 2*(*n); *lineptr = gk_realloc(*lineptr, (*n)*sizeof(char), "gk_getline: lineptr"); } - + if (ch == '\n') break; } @@ -377,7 +381,7 @@ int32_t *gk_i32readfilebin(char *fname, size_t *r_nelmnts) array = gk_i32malloc(nelmnts, "gk_i32readfilebin: array"); fpin = gk_fopen(fname, "rb", "gk_i32readfilebin"); - + if (fread(array, sizeof(int32_t), nelmnts, fpin) != nelmnts) { gk_errexit(SIGERR, "Failed to read the number of words requested. %zd\n", nelmnts); gk_free((void **)&array, LTERM); @@ -444,7 +448,7 @@ int64_t *gk_i64readfilebin(char *fname, size_t *r_nelmnts) array = gk_i64malloc(nelmnts, "gk_i64readfilebin: array"); fpin = gk_fopen(fname, "rb", "gk_i64readfilebin"); - + if (fread(array, sizeof(int64_t), nelmnts, fpin) != nelmnts) { gk_errexit(SIGERR, "Failed to read the number of words requested. %zd\n", nelmnts); gk_free((void **)&array, LTERM); @@ -511,7 +515,7 @@ ssize_t *gk_zreadfilebin(char *fname, size_t *r_nelmnts) array = gk_zmalloc(nelmnts, "gk_zreadfilebin: array"); fpin = gk_fopen(fname, "rb", "gk_zreadfilebin"); - + if (fread(array, sizeof(ssize_t), nelmnts, fpin) != nelmnts) { gk_errexit(SIGERR, "Failed to read the number of words requested. %zd\n", nelmnts); gk_free((void **)&array, LTERM); @@ -578,7 +582,7 @@ float *gk_freadfilebin(char *fname, size_t *r_nelmnts) array = gk_fmalloc(nelmnts, "gk_freadfilebin: array"); fpin = gk_fopen(fname, "rb", "gk_freadfilebin"); - + if (fread(array, sizeof(float), nelmnts, fpin) != nelmnts) { gk_errexit(SIGERR, "Failed to read the number of words requested. %zd\n", nelmnts); gk_free((void **)&array, LTERM); @@ -645,7 +649,7 @@ double *gk_dreadfilebin(char *fname, size_t *r_nelmnts) array = gk_dmalloc(nelmnts, "gk_dreadfilebin: array"); fpin = gk_fopen(fname, "rb", "gk_dreadfilebin"); - + if (fread(array, sizeof(double), nelmnts, fpin) != nelmnts) { gk_errexit(SIGERR, "Failed to read the number of words requested. %zd\n", nelmnts); gk_free((void **)&array, LTERM); @@ -678,4 +682,3 @@ size_t gk_dwritefilebin(char *fname, size_t n, double *a) return fsize; } - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8584820..164ebf5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,9 +11,9 @@ add_executable(cmpnbrs cmpnbrs.c) add_executable(splatt2svd splatt2svd.c) add_executable(gkuniq gkuniq.c) -foreach(prog strings gksort fis gkrw gkgraph csrcnv grKx m2mnbrs cmpnbrs splatt2svd gkuniq) - target_link_libraries(${prog} GKlib) -endforeach(prog) - -# Install a subset of them -install(TARGETS csrcnv RUNTIME DESTINATION bin) +foreach(prog IN ITEMS strings gksort fis gkrw gkgraph csrcnv grKx m2mnbrs cmpnbrs splatt2svd gkuniq) + target_link_libraries(${prog} PRIVATE GKlib) + if(NOT prog MATCHES "m2mnbrs|cmpnbrs|splatt2svd") + add_test(NAME ${prog} COMMAND ${prog}) + endif() +endforeach()