view add_windows_support.patch @ 554:3fd912875434

windows wip, get the lgtd_time_monotonic library fully working and tested
author Louis Opter <louis@opter.org>
date Thu, 18 May 2017 11:00:10 -0700
parents b3de1b255605
children
line wrap: on
line source

# HG changeset patch
# Parent  6ec1913ccfc63c8ca38368d04f885be5d59f8817
lightsd: get the build system and time_monotonic to work on Windows

This is the first step towards Windows portability.

diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,6 +19,12 @@
 
 ### Platform checks ############################################################
 
+INCLUDE(CheckFunctionExists)
+INCLUDE(CheckVariableExists)
+INCLUDE(TestBigEndian)
+
+TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM)
+
 # TODO: we need at least 2.0.19-stable because of the logging defines
 FIND_PACKAGE(Event2 REQUIRED COMPONENTS core)
 FIND_PACKAGE(Endian REQUIRED)
@@ -37,16 +43,10 @@
     INCLUDE(UseLATEX)
 ENDIF ()
 
-INCLUDE(CheckFunctionExists)
-INCLUDE(CheckVariableExists)
-INCLUDE(TestBigEndian)
-
 INCLUDE(CompatReallocArray)
 INCLUDE(CompatSetProctitle)
 INCLUDE(CompatTimeMonotonic)
 
-TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM)
-
 ### Global definitions #########################################################
 
 INCLUDE(AddAllSubdirectories)
@@ -54,11 +54,13 @@
 
 SET(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
 
-SET(CMAKE_C_FLAGS "-pipe ${CMAKE_C_FLAGS}")
-STRING(STRIP "${CMAKE_C_FLAGS}" CMAKE_C_FLAGS)
 SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}}")
 STRING(STRIP "${CMAKE_C_FLAGS}" CMAKE_C_FLAGS)
-SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99")
+IF (CMAKE_C_COMPILER_ID MATCHES "MSVC")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Wall")
+ELSE ()
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99 -pipe")
+ENDIF ()
 SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "")
 
 IF (CMAKE_SYSTEM_NAME MATCHES "Linux")
@@ -74,6 +76,13 @@
     ADD_DEFINITIONS("-D_DARWIN_C_SOURCE=1")
 ENDIF ()
 
+IF (MSVC)
+    ADD_DEFINITIONS(
+        "-D_CRT_SECURE_NO_WARNINGS"
+        "-DNOMINMAX"
+    )
+ENDIF ()
+
 ADD_DEFINITIONS(
     "-DLGTD_BIG_ENDIAN_SYSTEM=${BIG_ENDIAN_SYSTEM}"
     "-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}"
@@ -99,11 +108,12 @@
 
 INCLUDE_DIRECTORIES(
     ${EVENT2_INCLUDE_DIR}
+    ${LIGHTSD_SOURCE_DIR}
+    ${LIGHTSD_SOURCE_DIR}/compat/generic
+    ${LIGHTSD_BINARY_DIR}
     ${LIGHTSD_BINARY_DIR}/compat
-    ${LIGHTSD_BINARY_DIR}/compat/generic
 )
 
-ADD_SUBDIRECTORY(compat)
 ADD_SUBDIRECTORY(core)
 ADD_SUBDIRECTORY(lifx)
 
diff --git a/CMakeScripts/AddAllSubdirectories.cmake b/CMakeScripts/AddAllSubdirectories.cmake
--- a/CMakeScripts/AddAllSubdirectories.cmake
+++ b/CMakeScripts/AddAllSubdirectories.cmake
@@ -1,8 +1,12 @@
 FUNCTION(ADD_ALL_SUBDIRECTORIES)
     FILE(GLOB SUBDIRECTORIES "*")
     FOREACH (ENTRY ${SUBDIRECTORIES})
-        IF (IS_DIRECTORY ${ENTRY} AND EXISTS "${ENTRY}/CMakeLists.txt")
-            ADD_SUBDIRECTORY(${ENTRY})
+        IF (
+            IS_DIRECTORY "${ENTRY}" AND
+            NOT IS_SYMLINK "${ENTRY}" AND
+            EXISTS "${ENTRY}/CMakeLists.txt"
+        )
+            ADD_SUBDIRECTORY("${ENTRY}")
         ENDIF ()
     ENDFOREACH ()
 ENDFUNCTION()
diff --git a/CMakeScripts/CompatTimeMonotonic.cmake b/CMakeScripts/CompatTimeMonotonic.cmake
--- a/CMakeScripts/CompatTimeMonotonic.cmake
+++ b/CMakeScripts/CompatTimeMonotonic.cmake
@@ -1,8 +1,6 @@
 IF (NOT TIME_MONOTONIC_LIBRARY)
     SET(COMPAT_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.c")
-    SET(COMPAT_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.h")
     SET(GENERIC_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.c")
-    SET(GENERIC_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.h")
     SET(TIME_MONOTONIC_LIBRARY time_monotonic CACHE INTERNAL "lgtd_time_monotonic implementation")
 
     IF (APPLE)
@@ -18,6 +16,7 @@
     ENDIF ()
 
     SET(CMAKE_REQUIRED_QUIET TRUE)
+
     MESSAGE(STATUS "Looking for clock_gettime")
     CHECK_FUNCTION_EXISTS("clock_gettime" HAVE_CLOCK_GETTIME)
     IF (NOT HAVE_CLOCK_GETTIME)
@@ -32,19 +31,17 @@
             UNSET(TIME_MONOTONIC_LIBRARY_DEP CACHE)
         ENDIF ()
     ENDIF ()
-    UNSET(CMAKE_REQUIRED_QUIET)
+
     UNSET(CMAKE_REQUIRED_FLAGS)
 
     IF (HAVE_CLOCK_GETTIME)
         MESSAGE(STATUS "Looking for clock_gettime - found")
-        FILE(COPY "${GENERIC_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/")
         SET(
             TIME_MONOTONIC_IMPL ${GENERIC_TIME_MONOTONIC_IMPL}
             CACHE INTERNAL "lgtd_time_monotonic (POSIX generic source)"
         )
     ELSEIF (EXISTS "${COMPAT_TIME_MONOTONIC_IMPL}")
         MESSAGE(STATUS "Looking for clock_gettime - not found, using built-in compatibilty file")
-        FILE(COPY "${COMPAT_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/")
         SET(
             TIME_MONOTONIC_IMPL "${COMPAT_TIME_MONOTONIC_IMPL}"
             CACHE INTERNAL "lgtd_time_monotonic (${CMAKE_SYSTEM_NAME} specific source)"
@@ -52,9 +49,38 @@
     ELSE ()
         MESSAGE(SEND_ERROR "Looking for clock_gettime - not found")
     ENDIF ()
+
+    SET(COMPAT_SLEEP_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/sleep_monotonic.c")
+    SET(GENERIC_SLEEP_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/generic/sleep_monotonic.c")
+
+    MESSAGE(STATUS "Looking for nanosleep")
+    CHECK_FUNCTION_EXISTS("nanosleep" HAVE_NANOSLEEP)
+    IF (HAVE_NANOSLEEP)
+        MESSAGE(STATUS "Looking for nanosleep - found")
+        SET(
+            SLEEP_MONOTONIC_IMPL "${GENERIC_SLEEP_MONOTONIC_IMPL}"
+            CACHE INTERNAL "lgtd_sleep_monotonic (POSIX generic source)"
+        )
+    ELSEIF (EXISTS "${COMPAT_SLEEP_MONOTONIC_IMPL}")
+        MESSAGE(STATUS "Looking for nanosleep - not found, using built-in compatibilty file")
+        SET(
+            SLEEP_MONOTONIC_IMPL "${COMPAT_SLEEP_MONOTONIC_IMPL}"
+            CACHE INTERNAL "lgtd_sleep_monotonic (${CMAKE_SYSTEM_NAME} specific source)"
+        )
+        IF (WIN32)
+            SET(TIME_MONOTONIC_LIBRARY_DEP Winmm CACHE INTERNAL "dependency for lgtd_sleep_monotonic")
+        ENDIF ()
+    ENDIF ()
+
+    UNSET(CMAKE_REQUIRED_QUIET)
 ENDIF ()
 
-ADD_LIBRARY(${TIME_MONOTONIC_LIBRARY} STATIC "${TIME_MONOTONIC_IMPL}")
+ADD_LIBRARY(
+    ${TIME_MONOTONIC_LIBRARY}
+    STATIC
+    "${TIME_MONOTONIC_IMPL}"
+    "${SLEEP_MONOTONIC_IMPL}"
+)
 
 IF (TIME_MONOTONIC_LIBRARY_DEP)
     TARGET_LINK_LIBRARIES(${TIME_MONOTONIC_LIBRARY} ${TIME_MONOTONIC_LIBRARY_DEP})
diff --git a/CMakeScripts/FindEndian.cmake b/CMakeScripts/FindEndian.cmake
--- a/CMakeScripts/FindEndian.cmake
+++ b/CMakeScripts/FindEndian.cmake
@@ -12,10 +12,15 @@
     IF (HAVE_ENDIAN_H)
         MESSAGE(STATUS "Looking for endian.h - found")
         SET(ENDIAN_H_PATH "using native headers" CACHE INTERNAL "endian.h path")
-    ELSEIF (EXISTS "${COMPAT_ENDIAN_H}")
+    ELSEIF (EXISTS "${COMPAT_ENDIAN_H}" OR EXISTS "${COMPAT_ENDIAN_H}.in")
         MESSAGE(STATUS "Looking for endian.h - not found, using built-in compatibility file")
-        FILE(COPY "${COMPAT_ENDIAN_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/compat/")
-        SET(ENDIAN_H_PATH "${COMPAT_ENDIAN_H}" CACHE INTERNAL "endian.h path")
+        IF (EXISTS "${COMPAT_ENDIAN_H}")
+            FILE(COPY "${COMPAT_ENDIAN_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/compat/")
+            SET(ENDIAN_H_PATH "${COMPAT_ENDIAN_H}" CACHE INTERNAL "endian.h path")
+        ELSE ()
+            CONFIGURE_FILE("${COMPAT_ENDIAN_H}.in" "${LIGHTSD_BINARY_DIR}/compat/endian.h")
+            SET(ENDIAN_H_PATH "${COMPAT_ENDIAN_H}.in" CACHE INTERNAL "endian.h path")
+        ENDIF ()
     ELSE ()
         MESSAGE(STATUS "Looking for endian.h - not found")
     ENDIF ()
diff --git a/CMakeScripts/FindEvent2.cmake b/CMakeScripts/FindEvent2.cmake
--- a/CMakeScripts/FindEvent2.cmake
+++ b/CMakeScripts/FindEvent2.cmake
@@ -1,14 +1,21 @@
 FIND_PATH(
     EVENT2_INCLUDE_DIR
     event2/event.h
-    # OpenBSD has libevent1 in /usr/lib, always try /usr/local first:
-    HINTS /usr/local/
+    HINTS
+    /usr/local/ # OpenBSD has libevent1 in /usr/lib, always try /usr/local first
+    $ENV{EVENT2_DIR}/include # Windows...
 )
 
 FOREACH (COMPONENT ${Event2_FIND_COMPONENTS})
     STRING(TOUPPER ${COMPONENT} UPPER_COMPONENT)
     FIND_LIBRARY(
-        EVENT2_${UPPER_COMPONENT}_LIBRARY event_${COMPONENT} HINTS /usr/local/
+        EVENT2_${UPPER_COMPONENT}_LIBRARY
+        event_${COMPONENT}
+        NAMES
+        libevent_${COMPONENT} # Windows
+        HINTS
+        /usr/local/
+        $ENV{EVENT2_DIR}
     )
     IF (EVENT2_${UPPER_COMPONENT}_LIBRARY)
         SET(Event2_${COMPONENT}_FOUND TRUE)
diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt
deleted file mode 100644
--- a/compat/CMakeLists.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-ADD_SUBDIRECTORY(generic)
diff --git a/compat/Darwin/time_monotonic.c b/compat/Darwin/time_monotonic.c
--- a/compat/Darwin/time_monotonic.c
+++ b/compat/Darwin/time_monotonic.c
@@ -24,7 +24,7 @@
 #include <assert.h>
 #include <stdint.h>
 
-#include "time_monotonic.h"
+#include "core/time_monotonic.h"
 
 enum { MSECS_IN_NSEC = 1000000 };
 
diff --git a/compat/Darwin/time_monotonic.h b/compat/Darwin/time_monotonic.h
deleted file mode 100644
--- a/compat/Darwin/time_monotonic.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr>
-//
-// This file is part of lighstd.
-//
-// lighstd is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// lighstd is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with lighstd.  If not, see <http://www.gnu.org/licenses/>.
-
-#pragma once
-
-typedef uint64_t lgtd_time_mono_t;
-
-lgtd_time_mono_t lgtd_time_monotonic_msecs(void);
diff --git a/compat/Windows/endian.h.in b/compat/Windows/endian.h.in
new file mode 100644
--- /dev/null
+++ b/compat/Windows/endian.h.in
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <stdlib.h>
+
+#if @BIG_ENDIAN_SYSTEM@
+#   define htobe16(x) (x)
+#   define htole16(x) _byteswap_ushort(x)
+#   define be16toh(x) (x)
+#   define le16toh(x) _byteswap_ushort(x)
+
+#   define htobe32(x) (x)
+#   define htole32(x) _byteswap_ulong(x)
+#   define be32toh(x) (x)
+#   define le32toh(x) _byteswap_ulong(x)
+
+#   define htobe64(x) (x)
+#   define htole64(x) _byteswap_uint64(x)
+#   define be64toh(x) (x)
+#   define le64toh(x) _byteswap_uint64(x)
+#else
+#   define htobe16(x) _byteswap_ushort(x)
+#   define htole16(x) (x)
+#   define be16toh(x) _byteswap_ushort(x)
+#   define le16toh(x) (x)
+
+#   define htobe32(x) _byteswap_ulong(x)
+#   define htole32(x) (x)
+#   define be32toh(x) _byteswap_ulong(x)
+#   define le32toh(x) (x)
+
+#   define htobe64(x) _byteswap_uint64(x)
+#   define htole64(x) (x)
+#   define be64toh(x) _byteswap_uint64(x)
+#   define le64toh(x) (x)
+#endif
diff --git a/compat/Windows/sleep_monotonic.c b/compat/Windows/sleep_monotonic.c
new file mode 100644
--- /dev/null
+++ b/compat/Windows/sleep_monotonic.c
@@ -0,0 +1,63 @@
+// Copyright (c) 2017, Louis Opter <louis@opter.org>
+//
+// This file is part of lighstd.
+//
+// lighstd is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// lighstd is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with lighstd.  If not, see <http://www.gnu.org/licenses/>.
+
+// See:
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686298(v=vs.85).aspx
+// https://msdn.microsoft.com/en-us/library/dd743626(v=vs.85).aspx
+
+#include <Windows.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include "core/time_monotonic.h"
+#include "core/lightsd.h"
+
+enum { TIMER_RESOLUTION_MS = 1 };
+
+static UINT
+_lgtd_set_timer_resolution(UINT resolution_ms)
+{
+    TIMECAPS tc;
+    if (timeGetDevCaps(&tc, sizeof(tc)) != TIMERR_NOERROR) {
+        fprintf(stderr, "lightsd: multimedia timers unavailable.");
+        exit(1);
+    }
+
+    UINT resolution = LGTD_MAX(tc.wPeriodMin, resolution_ms);
+    resolution = LGTD_MIN(resolution, tc.wPeriodMax);
+    timeBeginPeriod(resolution);
+    return resolution;
+}
+
+static void
+_lgtd_reset_timer_resolution(UINT resolution_ms)
+{
+    if (timeEndPeriod(resolution_ms) != TIMERR_NOERROR) {
+        fprintf(stderr, "lightsd: can't reset timer resolution.");
+    }
+}
+
+void
+lgtd_sleep_monotonic_msecs(int msecs)
+{
+    UINT actual = _lgtd_set_timer_resolution(TIMER_RESOLUTION_MS);
+    Sleep(msecs);
+    _lgtd_reset_timer_resolution(actual);
+}
diff --git a/compat/Windows/time_monotonic.c b/compat/Windows/time_monotonic.c
new file mode 100644
--- /dev/null
+++ b/compat/Windows/time_monotonic.c
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, Louis Opter <louis@opter.org>
+//
+// This file is part of lighstd.
+//
+// lighstd is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// lighstd is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with lighstd.  If not, see <http://www.gnu.org/licenses/>.
+
+// This is pretty much "Using QPC in native code" from:
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
+
+#include <Windows.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "core/time_monotonic.h"
+
+lgtd_time_mono_t
+lgtd_time_monotonic_msecs(void)
+{
+    static LARGE_INTEGER frequency = { .QuadPart = 0 }; // ticks per second
+    if (frequency.QuadPart == 0
+        && QueryPerformanceFrequency(&frequency) == false) {
+        LPSTR msg = NULL;
+        DWORD msg_len = FormatMessageA(
+            FORMAT_MESSAGE_ALLOCATE_BUFFER
+            | FORMAT_MESSAGE_FROM_SYSTEM
+            | FORMAT_MESSAGE_IGNORE_INSERTS,
+            NULL,
+            GetLastError(),
+            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+            // By default the message is stored in the buffer pointed by
+            // lpMsgBuf, but we use FORMAT_MESSAGE_ALLOCATE_BUFFER to have
+            // the buffer allocated for us.
+            // So instead you need to give the function a pointer on your
+            // pointer instead of your buffer and do an ugly cast:
+            (LPSTR)&msg,
+            0,
+            NULL
+        );
+        if (!msg_len) {
+            msg = "unknown error";
+        }
+        fprintf(stderr, "lightsd: QPC timer unavailable: %s.\r\n", msg);
+        exit(1);
+    }
+
+    LARGE_INTEGER time;
+    QueryPerformanceCounter(&time);
+    // Multiply the number of ticks by 1000 first to get a final value in ms:
+    time.QuadPart *= 1000;
+    // Then (the order is important to avoid loosing precision) divide by the
+    // number of ticks per second:
+    time.QuadPart /= frequency.QuadPart;
+    return time.QuadPart;
+}
diff --git a/compat/generic/CMakeLists.txt b/compat/generic/CMakeLists.txt
deleted file mode 100644
--- a/compat/generic/CMakeLists.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-FILE(COPY sys DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/compat/generic/sleep_monotonic.c b/compat/generic/sleep_monotonic.c
new file mode 100644
--- /dev/null
+++ b/compat/generic/sleep_monotonic.c
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, Louis Opter <louis@opter.org>
+//
+// This file is part of lighstd.
+//
+// lighstd is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// lighstd is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with lighstd.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#include "core/time_monotonic.h"
+#include "core/lightsd.h"
+
+void
+lgtd_sleep_monotonic_msecs(int msecs)
+{
+    assert(msecs >= 0);
+
+    struct timespec remainder = LGTD_MSECS_TO_TIMESPEC(msecs),
+                    request = { 0, 0 },
+                    *rmtp = &remainder,
+                    *rqtp = &request;
+    do {
+        LGTD_SWAP(struct timespec *, rqtp, rmtp);
+        int err = nanosleep(rqtp, rmtp);
+        if (err && errno != EINTR) {
+            const char *reason = strerror(errno);
+            fprintf(stderr, "lightsd: nanosleep failed: %s.\n", reason);
+            abort();
+        }
+    } while (remainder.tv_sec > 0 && remainder.tv_nsec > 0);
+}
diff --git a/compat/generic/time_monotonic.c b/compat/generic/time_monotonic.c
--- a/compat/generic/time_monotonic.c
+++ b/compat/generic/time_monotonic.c
@@ -18,7 +18,7 @@
 #include <stdint.h>
 #include <time.h>
 
-#include "time_monotonic.h"
+#include "core/time_monotonic.h"
 
 lgtd_time_mono_t
 lgtd_time_monotonic_msecs(void)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -2,9 +2,7 @@
 # I only have lifx bulbs right now and I don't want to do any premature work.
 
 INCLUDE_DIRECTORIES(
-    ${CMAKE_CURRENT_SOURCE_DIR}/../
     ${CMAKE_CURRENT_SOURCE_DIR}
-    ${CMAKE_CURRENT_BINARY_DIR}/../
     ${CMAKE_CURRENT_BINARY_DIR}
 )
 
diff --git a/core/lightsd.h b/core/lightsd.h
--- a/core/lightsd.h
+++ b/core/lightsd.h
@@ -35,7 +35,7 @@
 #pragma once
 
 #ifndef __attribute__
-# define __atttribute__(e)
+# define __attribute__(e)
 #endif
 
 #define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1)
@@ -43,11 +43,16 @@
 #define LGTD_MAX(a, b) ((a) > (b) ? (a) : (b))
 
 #define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#define LGTD_SWAP(type, a, b) do { type _tmp = a; a = b; b = _tmp; } while (0)
 
 #define LGTD_MSECS_TO_TIMEVAL(v) {  \
     .tv_sec = (v) / 1000,           \
     .tv_usec = ((v) % 1000) * 1000  \
 }
+#define LGTD_MSECS_TO_TIMESPEC(v) {     \
+    .tv_sec = (v) / 1000,               \
+    .tv_nsec = ((v) % 1000) * 1000000   \
+}
 #define LGTD_NSECS_TO_USECS(v) ((v) / (unsigned int)1E6)
 #define LGTD_NSECS_TO_SECS(v) ((v) / (unsigned int)1E9)
 #define LGTD_SECS_TO_NSECS(v) ((v) * (unsigned int)1E9)
diff --git a/compat/generic/time_monotonic.h b/core/time_monotonic.h
rename from compat/generic/time_monotonic.h
rename to core/time_monotonic.h
--- a/compat/generic/time_monotonic.h
+++ b/core/time_monotonic.h
@@ -20,3 +20,4 @@
 typedef uint64_t lgtd_time_mono_t;
 
 lgtd_time_mono_t lgtd_time_monotonic_msecs(void);
+void lgtd_sleep_monotonic_msecs(int);
diff --git a/lifx/CMakeLists.txt b/lifx/CMakeLists.txt
--- a/lifx/CMakeLists.txt
+++ b/lifx/CMakeLists.txt
@@ -1,7 +1,5 @@
 INCLUDE_DIRECTORIES(
-    ${CMAKE_CURRENT_SOURCE_DIR}/../
     ${CMAKE_CURRENT_SOURCE_DIR}
-    ${CMAKE_CURRENT_BINARY_DIR}/../
     ${CMAKE_CURRENT_BINARY_DIR}
 )
 
diff --git a/tests/compat/CMakeLists.txt b/tests/compat/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/tests/compat/CMakeLists.txt
@@ -0,0 +1,1 @@
+ADD_ALL_SUBDIRECTORIES()
diff --git a/tests/compat/time_monotonic/CMakeLists.txt b/tests/compat/time_monotonic/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/tests/compat/time_monotonic/CMakeLists.txt
@@ -0,0 +1,8 @@
+FUNCTION(ADD_TIME_MONOTONIC_TEST TEST_SOURCE)
+    ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} ${TIME_MONOTONIC_LIBRARY})
+ENDFUNCTION()
+
+FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c")
+FOREACH(TEST ${TESTS})
+    ADD_TIME_MONOTONIC_TEST(${TEST})
+ENDFOREACH()
diff --git a/tests/compat/time_monotonic/test_time_monotonic_msecs.c b/tests/compat/time_monotonic/test_time_monotonic_msecs.c
new file mode 100644
--- /dev/null
+++ b/tests/compat/time_monotonic/test_time_monotonic_msecs.c
@@ -0,0 +1,17 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "core/time_monotonic.h"
+#include "core/lightsd.h"
+
+int
+main(void)
+{
+    lgtd_time_mono_t start = lgtd_time_monotonic_msecs();
+    lgtd_sleep_monotonic_msecs(50);
+    int duration = (int)(lgtd_time_monotonic_msecs() - start);
+    printf("slept for %dms\r\n", duration);
+    return LGTD_ABS(duration - 50) > 10;
+}
diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt
--- a/tests/core/CMakeLists.txt
+++ b/tests/core/CMakeLists.txt
@@ -1,9 +1,7 @@
 INCLUDE_DIRECTORIES(
-    ${LIGHTSD_SOURCE_DIR}
     ${LIGHTSD_SOURCE_DIR}/core/
     ${CMAKE_CURRENT_SOURCE_DIR}
     ${CMAKE_CURRENT_SOURCE_DIR}/../lifx
-    ${LIGHTSD_BINARY_DIR}
     ${LIGHTSD_BINARY_DIR}/core/
     ${CMAKE_CURRENT_BINARY_DIR}
     ${CMAKE_CURRENT_BINARY_DIR}/../lifx
diff --git a/tests/lifx/CMakeLists.txt b/tests/lifx/CMakeLists.txt
--- a/tests/lifx/CMakeLists.txt
+++ b/tests/lifx/CMakeLists.txt
@@ -1,9 +1,7 @@
 INCLUDE_DIRECTORIES(
-    ${LIGHTSD_SOURCE_DIR}
     ${LIGHTSD_SOURCE_DIR}/lifx/
     ${CMAKE_CURRENT_SOURCE_DIR}
     ${CMAKE_CURRENT_SOURCE_DIR}/../core
-    ${LIGHTSD_BINARY_DIR}
     ${LIGHTSD_BINARY_DIR}/lifx/
     ${CMAKE_CURRENT_BINARY_DIR}
     ${CMAKE_CURRENT_BINARY_DIR}/../core