Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@
^\.vscode$
^\.claude$
^CLAUDE.md$
^w-build$
^w-install$
3 changes: 1 addition & 2 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ jobs:
config:
- {os: ubuntu-24.04-arm, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}
- {os: ubuntu-22.04, r: 'oldrel-3'}
- {os: macOS-latest, r: 'release'}
- {os: macos-15-intel, r: 'oldrel-2'}
- {os: windows-latest, r: 'release'}
- {os: windows-latest, r: 'oldrel-4'}
- {os: windows-latest, r: 'oldrel-5'}

env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
.Rhistory
.RData
.Ruserdata
/w-build/
/w-install/
/docs/
/revdep/
/.claude/
Expand Down
63 changes: 49 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,51 @@ The package uses GitHub Actions workflows in `.github/workflows/`:

### Build System

**Configure Scripts:**
- `configure` (Linux/macOS): Detects system-installed libfswatch in standard locations (`/usr/local`, `/usr`, homebrew paths). If not found, compiles bundled libfswatch (v1.19.0-dev) using cmake. Handles special cases like ARM atomic operations.
- `configure.win` (Windows non-UCRT): Compiles libfswatch from source for both x64 and i386 architectures
- `configure.ucrt` (Windows UCRT): Simplified version for modern Windows R builds
- All scripts generate `src/Makevars` with appropriate compiler flags

**Key Dependencies:**
- libfswatch (bundled source in `src/fswatch/`)
- cmake (required for compiling libfswatch from source)
- pthread (for background monitoring thread)
- 'later' R package (for async callback execution)
The bundled libfswatch sources (`src/fswatch/`) are compiled **directly into the
package shared object** alongside `init.c`/`watcher.c` — no cmake, no static
archive, no separate library. Platform feature selection comes from a
hand-maintained config header, not host probing.

**Configure scripts:**
- `configure` (Linux/macOS/other Unix): detects a system libfswatch
(`/usr/local`, `/usr`, Homebrew); if absent, sets up the in-place bundled
build. Probes for `-latomic` (ARM) and adds `-framework CoreServices` (macOS),
emits the `CXX_STD` line (only on R < 4.3), and substitutes `src/Makevars.in`
→ `src/Makevars`.
- `configure.ucrt` (Windows UCRT): computes only the `CXX_STD` line,
substituting `src/Makevars.ucrt.in` → `src/Makevars.ucrt`.
- Legacy non-UCRT Windows has no `configure.win`; it uses the static
`src/Makevars.win`, and `Biarch: true` keeps the dual i386/x64 build.

**Key pieces:**
- `src/fswatch/.../libfswatch_config.h`: hand-maintained; selects `HAVE_*`
features from compiler platform macros (replaces the cmake/autotools probe).
- `src/Makevars{.in,.win,.ucrt.in}`: declare the bundled objects and carry one
portable explicit compile rule per object (no GNU-make extensions). The POSIX
object list is `tools/fsw_objects_posix.list`; the Windows object list is
separate because those files include `<windows.h>`.
- `src/link.cpp`: empty `.cpp` that forces R to link with the C++ linker.
- `tools/update_libfswatch.sh`: re-vendors libfswatch and regenerates the config
header, object list, and Makevars; calls `tools/patch_libfswatch.sh`.
- `tools/patch_libfswatch.sh`: idempotent source patches — a null-format guard
in `string_utils`, self-guards on the fsevents/inotify/fanotify monitors, and
neutering of libfswatch's stdout/stderr/cerr logging (for the R CMD check
"compiled code" policy).

**Key dependencies:** an R C/C++ toolchain plus `make`; pthread; the
'later' R package. A system libfswatch is used automatically when present.

### C++ Standard and Minimum R Version

The bundled libfswatch uses `std::filesystem` (in `path_utils`, `poll_monitor`,
and every monitor's `scan()`), which mandates **C++17** — there is no portable
pre-C++17 substitute. Because the package now compiles libfswatch itself rather
than via cmake, it relies on R's own `CXX_STD = CXX17`, support for which was
added in **R 3.5.0**. That is precisely why `DESCRIPTION` requires
`R (>= 3.5)`: it is the lowest R that can build the package (R < 3.5 cannot
request C++17), not an arbitrary floor. `configure`/`configure.ucrt` emit
`CXX_STD = CXX17` only on R < 4.3, since R >= 4.3 defaults to C++17 and
specifying it then trips a CRAN "drop specification unless essential" NOTE.

### Event Filtering

Expand All @@ -102,12 +136,13 @@ The package filters filesystem events to only report main event types (Created,
### Windows
- Uses ReadDirectoryChangesW API (always recursive)
- Windows latency has been specifically addressed (see NEWS.md - patch in v0.1.4.9000)
- Builds require cmake and compile libfswatch from bundled source
- Compiles the bundled libfswatch in place: UCRT via `Makevars.ucrt` (generated
by `configure.ucrt`), legacy non-UCRT via the static `Makevars.win` (gcc 8.3
needs `-lstdc++fs` for `std::filesystem`); `Biarch: true` builds i386 + x64

### macOS
- Uses FSEvents API (always recursive)
- Can use system libfswatch if installed via homebrew/MacPorts
- MACOSX_DEPLOYMENT_TARGET is automatically extracted from compiler flags
- Links `-framework CoreServices`; can use a system libfswatch (Homebrew/MacPorts)

### Linux
- Uses inotify API
Expand Down
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ Config/roxygen2/version: 8.0.0
Config/testthat/edition: 3
Config/usethis/last-upkeep: 2025-04-23
Encoding: UTF-8
SystemRequirements: 'libfswatch', or 'cmake' to compile from package
sources
SystemRequirements: 'libfswatch' (optional, bundled copy compiled
otherwise)
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# watcher (development version)

* Building the bundled 'libfswatch' from source no longer requires 'cmake', now directly using the R C/C++ toolchain.
A system 'libfswatch' is still used when one is available.

# watcher 0.1.6

* Updates bundled 'libfswatch' source to 1.20.1 release.
Expand Down
1 change: 0 additions & 1 deletion README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ watcher requires the 'libfswatch' library.

- On Linux / MacOS, an installed version will be used if found in the standard filesystem locations.
- On Windows, or if not found, the bundled version of 'libfswatch' 1.20.1 will be compiled from source.
- Source compilation of the library requires 'cmake'.

## Quick Start

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ watcher requires the ‘libfswatch’ library.
standard filesystem locations.
- On Windows, or if not found, the bundled version of ‘libfswatch’
1.20.1 will be compiled from source.
- Source compilation of the library requires ‘cmake’.

## Quick Start

Expand Down
3 changes: 2 additions & 1 deletion cleanup
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#!/bin/sh
rm -rf src/Makevars w-build w-install
rm -f src/Makevars src/*.so
find src -name '*.o' -delete 2>/dev/null
3 changes: 2 additions & 1 deletion cleanup.win
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
rm -rf src/Makevars w-build w-install
rm -f src/Makevars src/Makevars.ucrt src/*.dll
find src -name '*.o' -delete 2>/dev/null
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ coverage:
target: auto
threshold: 1%
informational: true
ignore:
- "src/fswatch"
73 changes: 31 additions & 42 deletions configure
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
#!/bin/sh

# Initialise
PKG_CFLAGS=""
PKG_LIBS="-lpthread"

# Helper function: detect cmake
detect_cmake() {
if which cmake > /dev/null 2>&1; then
return 0
fi
export PATH=$PATH:/Applications/CMake.app/Contents/bin
if which cmake > /dev/null 2>&1; then
return 0
fi
echo "Required 'cmake' not found"
exit 1
}

# Helper function: find library paths
# Usage: find_lib_paths <lib_name> <header_subdir> <default_libs>
# Sets: LIB_CFLAGS, LIB_LIBS
Expand Down Expand Up @@ -51,9 +37,11 @@ CPPFLAGS=`"${R_HOME}/bin/R" CMD config CPPFLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
export CC CFLAGS CPPFLAGS LDFLAGS

if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then
export MACOSX_DEPLOYMENT_TARGET=`echo $CC | sed -En 's/.*-version-min=([0-9][0-9.]*).*/\1/p'`
fi
# C++ standard: the bundled libfswatch requires C++17. R >= 4.3 compiles C++ as
# C++17 by default, so requesting it explicitly is redundant and trips a CRAN
# "please drop specification unless essential" NOTE. Only emit CXX_STD on older
# R, where the default is below C++17.
CXX_STD_LINE=`"${R_HOME}/bin/Rscript" -e 'cat(if (getRversion() < "4.3.0") "CXX_STD = CXX17" else "")'`

# Detect -latomic linker flag for ARM architectures (Raspberry Pi etc.)
echo "#include <stdint.h>
Expand All @@ -66,14 +54,14 @@ if [ $? -ne 0 ]; then
PKG_LIBS="$PKG_LIBS -latomic"
fi

# Determine whether to compile bundled library
# Determine whether to compile the bundled library
compile_fswatch=0

if [ -n "$WATCHER_LIBS" ]; then
echo "WATCHER_LIBS is set... building from source"
compile_fswatch=1
else
# Find libfswatch
# Find a system libfswatch
find_lib_paths "fswatch" "libfswatch" "-lfswatch"
FSWATCH_CFLAGS="$LIB_CFLAGS"
FSWATCH_LIBS="$LIB_LIBS"
Expand All @@ -88,36 +76,37 @@ int main() {
compile_fswatch=1
else
echo "Found 'libfswatch' $FSWATCH_CFLAGS"
PKG_CFLAGS="$FSWATCH_CFLAGS $PKG_CFLAGS"
PKG_LIBS="$FSWATCH_LIBS $PKG_LIBS"
fi
fi

# Compile libfswatch if needed
if [ $compile_fswatch -eq 1 ]; then
echo "Compiling 'libfswatch' from source ..."
detect_cmake
cmake -S src/fswatch -B w-build \
-DCMAKE_INSTALL_PREFIX=w-install \
-DCMAKE_INSTALL_LIBDIR=lib \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POSITION_INDEPENDENT_CODE=1 \
-DCMAKE_COLOR_MAKEFILE=0 \
-DCMAKE_INSTALL_MESSAGE=NEVER \
-DBUILD_LIBS_ONLY=1 \
-DUSE_NLS=0
cmake --build w-build --target install
rm -rf w-build
fi

# Set flags for bundled library
if [ -d "w-install/lib" ]; then
PKG_CFLAGS="-I../w-install/include $PKG_CFLAGS"
PKG_LIBS="../w-install/lib/libfswatch.a $PKG_LIBS"
if [ "$compile_fswatch" -eq 1 ]; then
# Compile the bundled libfswatch in place: put its source tree on the include
# path and build the POSIX object list (common core + the self-guarding POSIX
# monitors). The monitors self-guard on platform macros, so no uname-based
# source selection is needed.
echo "Compiling bundled 'libfswatch' in place ..."
PKG_CPPFLAGS="-Ifswatch/libfswatch/src"
# Single whitespace-separated line: embedded newlines would terminate the make
# variable assignment.
FSW_OBJECTS=`tr '\n' ' ' < tools/fsw_objects_posix.list | sed 's/ *$//'`
case `uname -s` in
Darwin) PKG_LIBS="$PKG_LIBS -framework CoreServices" ;;
esac
else
# System libfswatch: compile against ITS headers only. The bundled tree is
# deliberately kept off the include path so the package compiles against the
# same libfswatch version it links. No bundled objects are built.
PKG_CPPFLAGS="$FSWATCH_CFLAGS"
FSW_OBJECTS=""
fi

# Write to Makevars
sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars
sed -e "s|@cppflags@|$PKG_CPPFLAGS|" \
-e "s|@libs@|$PKG_LIBS|" \
-e "s|@fsw_objects@|$FSW_OBJECTS|" \
-e "s|@cxx_std@|$CXX_STD_LINE|" \
src/Makevars.in > src/Makevars

# Success
exit 0
31 changes: 12 additions & 19 deletions configure.ucrt
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
# Find compiler and export flags
CC=`"${R_HOME}/bin/R" CMD config CC`
CFLAGS=`"${R_HOME}/bin/R" CMD config CFLAGS`
CPPFLAGS=`"${R_HOME}/bin/R" CMD config CPPFLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
export CC CFLAGS CPPFLAGS LDFLAGS
#!/bin/sh

echo "Compiling 'libfswatch' from source ..."
cmake -S src/fswatch -B w-build -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX=w-install \
-DCMAKE_INSTALL_LIBDIR=lib \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POSITION_INDEPENDENT_CODE=1 \
-DCMAKE_COLOR_MAKEFILE=0 \
-DCMAKE_INSTALL_MESSAGE=NEVER \
-DBUILD_LIBS_ONLY=1 \
-DUSE_NLS=0
cmake --build w-build --target install
rm -rf w-build
# UCRT (Rtools42+) configure: the only thing to compute on Windows is the C++
# standard request. R >= 4.3 compiles C++ as C++17 by default, so specifying
# CXX_STD = CXX17 explicitly trips a CRAN "please drop specification unless
# essential" NOTE. Emit CXX_STD only on older R (R 4.2), where the default is
# below the C++17 that the bundled libfswatch needs. Everything else in
# Makevars.ucrt is static (legacy Makevars.win stays fully static -- Rtools40
# only ever serves R <= 4.1).

CXX_STD_LINE=`"${R_HOME}/bin/Rscript" -e 'cat(if (getRversion() < "4.3.0") "CXX_STD = CXX17" else "")'`

sed -e "s|@cxx_std@|$CXX_STD_LINE|" src/Makevars.ucrt.in > src/Makevars.ucrt

# Success
exit 0
30 changes: 0 additions & 30 deletions configure.win

This file was deleted.

1 change: 1 addition & 0 deletions src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.so
*.dll
Makevars
Makevars.ucrt
Loading
Loading