Skip to content
Open
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
6 changes: 3 additions & 3 deletions apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2523,7 +2523,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FBLazyVector: c00c20551d40126351a6783c47ce75f5b374851b
hermes-engine: 91023181d4bc5948b457de5314623fbfe4f8604e
hermes-engine: 146211e12d60a1951d9eb0287be07211e86cf5d5
RCTDeprecation: 3bb167081b134461cfeb875ff7ae1945f8635257
RCTRequired: 74839f55d5058a133a0bc4569b0afec750957f64
RCTSwiftUI: 87a316382f3eab4dd13d2a0d0fd2adcce917361a
Expand All @@ -2532,7 +2532,7 @@ SPEC CHECKSUMS:
React: 1b1536b9099195944034e65b1830f463caaa8390
React-callinvoker: 6dff6d17d1d6cc8fdf85468a649bafed473c65f5
React-Core: 00faa4d038298089a1d5a5b21dde8660c4f0820d
React-Core-prebuilt: a6d614de037caff7898424dfc22915ec792de921
React-Core-prebuilt: ef40616103ee11f8c2517697c3aa4f48ce790549
React-CoreModules: a17807f849bfd86045b0b9a75ec8c19373b482f6
React-cxxreact: c7b53ace5827be54048288bce5c55f337c41e95f
React-debug: e1f00fcd2cef58a2897471a6d76a4ef5f5f90c74
Expand Down Expand Up @@ -2596,7 +2596,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 5787b37b8e2e51dfeab697ec031cc7c4080dcea2
ReactCodegen: d07ee3c8db75b43d1cbe479ae6affebf9925c733
ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360
ReactNativeDependencies: 4d5ce2683b6d74f7c686bf90a88c7d381295cf3c
ReactNativeDependencies: 54189f1570b1308686cb21564e755e1daa77ea03
RNAudioAPI: 50957b72cc742b9aa1e05349be71b9db73c9cf74
RNAudioWorklets: ff0c53fd3c3bbffacb7dd3beb03ffe0ea9f1fd05
RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"validate:graph": "bash scripts/validate.sh --graph",
"validate:android": "bash scripts/validate.sh --android",
"validate:ios": "bash scripts/validate.sh --ios",
"validate:full": "bash scripts/validate.sh --full"
"validate:full": "bash scripts/validate.sh --full",
"setup:clangd": "bash packages/react-native-audio-api/common/cpp/clangd/generate-and-copy.sh",
"setup:clangd:clean": "bash packages/react-native-audio-api/common/cpp/clangd/generate-and-copy.sh --clean"
},
"devDependencies": {
"@commitlint/config-conventional": "17.0.2",
Expand Down
10 changes: 9 additions & 1 deletion packages/react-native-audio-api/.clangd
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ Documentation:

CompileFlags:
Add:
- -std=c++20
- -Wall

Diagnostics:
ClangTidy:
FastCheckFilter: Loose

---
# clang rejects -std=c++20 outright for plain Objective-C (.m).
If:
PathExclude: .*\.m
CompileFlags:
Add:
- -std=c++20

141 changes: 135 additions & 6 deletions packages/react-native-audio-api/common/cpp/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(COMMON_CPP_DIR ${CMAKE_SOURCE_DIR}/..)

# Toggle: set to 1 to enable test mode, 0 for normal mode
# =1 swaps real types for dummy stand-ins too (e.g. JsiBuffer), so production
# code needs =0; only test/*.cpp needs =1 — split via separate targets below.
set(RN_AUDIO_API_TEST 0)
set(REACT_NATIVE_DIR ../../../../../node_modules/react-native)

Expand All @@ -19,10 +20,11 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# -------------------------------------

file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/audioapi/*.cpp" "${COMMON_CPP_DIR}/audioapi/*.c" "${COMMON_CPP_DIR}/test/*.cpp")
file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/audioapi/*.cpp" "${COMMON_CPP_DIR}/audioapi/*.c")
list(REMOVE_ITEM COMMON_CPP_SOURCES
"${COMMON_CPP_DIR}/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp"
)
file(GLOB_RECURSE TEST_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/test/*.cpp")

set(INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include)
set(FFMPEG_INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include_ffmpeg)
Expand All @@ -45,9 +47,7 @@ target_compile_definitions(rnaudioapi_cursor PRIVATE
RN_AUDIO_API_TEST=${RN_AUDIO_API_TEST}
)

target_include_directories(
rnaudioapi_cursor
PRIVATE
set(COMMON_EXTRA_INCLUDES
"${COMMON_CPP_DIR}"
"${INCLUDE_DIR}"
"${INCLUDE_DIR}/opus"
Expand All @@ -58,7 +58,136 @@ target_include_directories(
"${COMMON_CPP_DIR}/../../ios"
"${googletest_SOURCE_DIR}/googletest/include"
)
target_include_directories(rnaudioapi_cursor PRIVATE ${COMMON_EXTRA_INCLUDES})

target_include_directories(rnaudioapi_cursor PUBLIC ${INCLUDE_LIBRARIES})

target_link_libraries(rnaudioapi_cursor ${LINK_LIBRARIES} gtest gmock gtest_main)
target_link_libraries(rnaudioapi_cursor ${LINK_LIBRARIES} gtest gmock gtest_main)

# Mirrors common/cpp/test/CMakeLists.txt's definitions exactly.
add_library(rnaudioapi_cursor_test OBJECT ${TEST_CPP_SOURCES})
target_compile_definitions(rnaudioapi_cursor_test PRIVATE
RN_AUDIO_API_TEST=1
RN_AUDIO_API_FFMPEG_DISABLED=1
RN_AUDIO_API_STATIC_EXTERNAL_LIBS_DISABLED=1
MA_NO_LIBOPUS=1
MA_NO_LIBVORBIS=1
)
target_include_directories(rnaudioapi_cursor_test PRIVATE ${COMMON_EXTRA_INCLUDES})
# OBJECT libs never link, but this still pulls in gtest/gmock's include dirs.
target_link_libraries(rnaudioapi_cursor_test PRIVATE gtest gmock gtest_main)

# Headers have no compile command of their own, so clangd falls back to
# "inferred from <whatever .cpp/.mm you most recently opened>" — session-order
# dependent and breaks badly across platforms (e.g. borrowed iOS flags can
# lose <span>). Force real per-header entries instead of relying on that.
file(GLOB_RECURSE COMMON_CPP_HEADERS CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/audioapi/*.h" "${COMMON_CPP_DIR}/audioapi/*.hpp")
# Vendored code isn't self-contained (e.g. FFmpeg needs -D__STDC_CONSTANT_MACROS
# from its caller) and was never meant to compile standalone.
list(FILTER COMMON_CPP_HEADERS EXCLUDE REGEX "/audioapi/external/")
list(FILTER COMMON_CPP_HEADERS EXCLUDE REGEX "/audioapi/libs/")
list(FILTER COMMON_CPP_HEADERS EXCLUDE REGEX "/audioapi/dsp/r8brain/")

add_library(rnaudioapi_cursor_headers OBJECT ${COMMON_CPP_HEADERS})
set_source_files_properties(${COMMON_CPP_HEADERS} PROPERTIES
HEADER_FILE_ONLY OFF
LANGUAGE CXX
)
target_compile_definitions(rnaudioapi_cursor_headers PRIVATE
RN_AUDIO_API_TEST=${RN_AUDIO_API_TEST}
)
target_include_directories(rnaudioapi_cursor_headers PRIVATE ${COMMON_EXTRA_INCLUDES})

# Android JNI glue (android/src/main/cpp/audioapi) isn't built here: it only
# compiles correctly with the NDK's own clang++ (--target/--sysroot); bolting
# the NDK sysroot onto AppleClang breaks even <string>/<cmath>.
# generate-and-copy.sh merges in Gradle's real android/.cxx/ commands instead.

# iOS Objective-C++ glue (ios/audioapi/ios) — OBJECT libs purely for compile
# commands; never actually linked.
if(APPLE)
enable_language(OBJCXX)
enable_language(OBJC)

set(IOS_DIR ${COMMON_CPP_DIR}/../../ios)
# Podspec compiles both .mm and plain .m (NativeAudioPlayer.m etc). Separate
# targets: a shared lib would leak CMAKE_CXX_STANDARD's -std=c++20 onto .m,
# which clang rejects outright for plain Objective-C.
file(GLOB_RECURSE IOS_MM_SOURCES CONFIGURE_DEPENDS "${IOS_DIR}/audioapi/ios/*.mm")
file(GLOB_RECURSE IOS_M_SOURCES CONFIGURE_DEPENDS "${IOS_DIR}/audioapi/ios/*.m")

add_library(rnaudioapi_cursor_ios OBJECT ${IOS_MM_SOURCES})
add_library(rnaudioapi_cursor_ios_objc OBJECT ${IOS_M_SOURCES})
set_source_files_properties(${IOS_MM_SOURCES} ${IOS_M_SOURCES} PROPERTIES COMPILE_FLAGS "-fobjc-arc")

target_compile_definitions(rnaudioapi_cursor_ios PRIVATE
RN_AUDIO_API_TEST=${RN_AUDIO_API_TEST}
)
target_compile_definitions(rnaudioapi_cursor_ios_objc PRIVATE
RN_AUDIO_API_TEST=${RN_AUDIO_API_TEST}
)

# System frameworks (Foundation, AVFoundation, AudioToolbox, ...) are found
# automatically once -isysroot points at an SDK.
execute_process(
COMMAND xcrun --sdk iphonesimulator --show-sdk-path
OUTPUT_VARIABLE IOS_SDK_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(IOS_SDK_PATH)
target_compile_options(rnaudioapi_cursor_ios PRIVATE -isysroot "${IOS_SDK_PATH}")
target_compile_options(rnaudioapi_cursor_ios_objc PRIVATE -isysroot "${IOS_SDK_PATH}")
endif()

# Reuse whatever `pod install` already resolved for the example app rather
# than re-deriving every pod's header path by hand. Requires having run it.
set(FABRIC_EXAMPLE_IOS_DIR ${COMMON_CPP_DIR}/../../../../apps/fabric-example/ios)
set(PODS_ROOT "${FABRIC_EXAMPLE_IOS_DIR}/Pods")
set(PODS_XCCONFIG "${FABRIC_EXAMPLE_IOS_DIR}/Pods/Target Support Files/Pods-FabricExample/Pods-FabricExample.debug.xcconfig")

set(IOS_POD_INCLUDE_DIRS "")
if(EXISTS "${PODS_XCCONFIG}")
file(STRINGS "${PODS_XCCONFIG}" HEADER_SEARCH_LINE REGEX "^HEADER_SEARCH_PATHS")
string(REGEX REPLACE "^HEADER_SEARCH_PATHS *= *" "" HEADER_SEARCH_LINE "${HEADER_SEARCH_LINE}")
string(REPLACE "\"" "" HEADER_SEARCH_LINE "${HEADER_SEARCH_LINE}")
string(REPLACE "$(inherited)" "" HEADER_SEARCH_LINE "${HEADER_SEARCH_LINE}")
string(REPLACE "\${PODS_ROOT}" "${PODS_ROOT}" HEADER_SEARCH_LINE "${HEADER_SEARCH_LINE}")
string(REPLACE "$(PODS_ROOT)" "${PODS_ROOT}" HEADER_SEARCH_LINE "${HEADER_SEARCH_LINE}")
separate_arguments(IOS_POD_INCLUDE_DIRS UNIX_COMMAND "${HEADER_SEARCH_LINE}")
else()
message(WARNING "clangd/CMakeLists.txt: ${PODS_XCCONFIG} not found — run `pod install` in apps/fabric-example/ios to resolve React/Pod headers for iOS files.")
endif()

set(IOS_EXTRA_INCLUDES
"${COMMON_CPP_DIR}"
"${IOS_DIR}"
# Xcode's headermap resolves bare `#import <NativeAudioPlayer.h>` via
# CocoaPods' header_mappings_dir; a plain -I needs the containing folder.
"${IOS_DIR}/audioapi/ios/core"
"${IOS_DIR}/audioapi/ios/system"
${IOS_POD_INCLUDE_DIRS}
)
target_include_directories(rnaudioapi_cursor_ios PRIVATE ${IOS_EXTRA_INCLUDES})
target_include_directories(rnaudioapi_cursor_ios_objc PRIVATE ${IOS_EXTRA_INCLUDES})

# Same reasoning as the common headers above. These headers also rely on
# their .mm caller having already #imported AVFoundation (see the
# `#ifndef __OBJC__` guard in e.g. IOSRecorderCallback.h) — `-include`
# reproduces that here.
file(GLOB_RECURSE IOS_HEADERS CONFIGURE_DEPENDS "${IOS_DIR}/audioapi/ios/*.h")

add_library(rnaudioapi_cursor_ios_headers OBJECT ${IOS_HEADERS})
set_source_files_properties(${IOS_HEADERS} PROPERTIES
HEADER_FILE_ONLY OFF
LANGUAGE OBJCXX
COMPILE_FLAGS "-include AVFoundation/AVFoundation.h"
)
target_compile_definitions(rnaudioapi_cursor_ios_headers PRIVATE
RN_AUDIO_API_TEST=${RN_AUDIO_API_TEST}
)
target_include_directories(rnaudioapi_cursor_ios_headers PRIVATE ${IOS_EXTRA_INCLUDES})
if(IOS_SDK_PATH)
target_compile_options(rnaudioapi_cursor_ios_headers PRIVATE -isysroot "${IOS_SDK_PATH}")
endif()
endif()
19 changes: 16 additions & 3 deletions packages/react-native-audio-api/common/cpp/clangd/SETUP.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
`CMakeLists.txt` in this directory can be used to build the C++ side and generate `compile_commands.json` for the Cursor C++ extension.
`CMakeLists.txt` in this directory can be used to build the C++ side and generate `compile_commands.json` for clangd (VS Code, Cursor, etc.).

**Generate compile_commands.json**

From this directory (`common/cpp/cursor`):
```bash
yarn setup:clangd
```

Generates entries for `common/cpp` (sources + headers), the iOS Objective-C++/Objective-C glue (`ios/audioapi/ios`), and merges in Android's real NDK-compiled entries from `android/.cxx` (see `generate-and-copy.sh`), then copies the result to the repo root.

**Prerequisites**

- **Android**: needs a real Gradle build's `android/.cxx` output — this CMakeLists.txt can't safely reproduce NDK cross-compile flags itself.
- **iOS**: needs `apps/fabric-example/ios/Pods` — reuses its generated xcconfig for React/Pod header paths.

If either is missing or stale (e.g. after a dependency bump), refresh both first:

```bash
./generate-and-copy.sh
yarn setup:clangd:clean
```

This runs `pod deintegrate && pod install` and a full Android build, then regenerates — slow, only needed when native deps/config actually change.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
PACKAGE_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
WORKLETS_DB="$REPO_ROOT/packages/react-native-audio-worklets/compile_commands.json"

# --clean: refresh the real Pods/Gradle builds this script depends on before
# regenerating. Slow — only needed after native deps/config actually change.
if [[ "${1:-}" == "--clean" ]]; then
(cd "$REPO_ROOT/apps/fabric-example/ios" && pod deintegrate && pod install)
(cd "$REPO_ROOT" && yarn workspace react-native-audio-api run build:android)
fi

cmake -B build .
cp build/compile_commands.json "$REPO_ROOT/compile_commands.json"

Expand All @@ -28,3 +36,41 @@ root_db.write_text(json.dumps(list(by_file.values()), indent=2) + "\n")
print(f"Preserved worklets entries in {root_db} (total {len(by_file)})")
PY
fi

# Merge in Android compile commands Gradle already produced under android/.cxx
# — those sources need the NDK's own clang++ (--target/--sysroot), which this
# CMakeLists.txt can't reproduce safely with the host compiler. Requires
# having built the Android app (or opened it in Android Studio) once.
python3 - <<PY
import json
from pathlib import Path

repo_root = Path("$REPO_ROOT")
package_dir = Path("$PACKAGE_DIR")
root_db = repo_root / "compile_commands.json"

android_dbs = list(package_dir.glob("android/.cxx/*/*/arm64-v8a/compile_commands.json"))
if not android_dbs:
print("No Android compile_commands.json found under android/.cxx — build the Android app once to populate it.")
else:
newest = max(android_dbs, key=lambda p: p.stat().st_mtime)
entries = json.loads(newest.read_text())

# Rewrite the node_modules symlink path Gradle resolved through to the
# real path, so clangd matches the files open in the editor.
node_modules_dir = str(repo_root / "node_modules" / "react-native-audio-api")
real_dir = str(package_dir)

by_file = {e["file"]: e for e in json.loads(root_db.read_text())}
merged = 0
for entry in entries:
if node_modules_dir not in entry["file"]:
continue
entry["file"] = entry["file"].replace(node_modules_dir, real_dir)
entry["command"] = entry["command"].replace(node_modules_dir, real_dir)
by_file[entry["file"]] = entry
merged += 1

root_db.write_text(json.dumps(list(by_file.values()), indent=2) + "\n")
print(f"Merged {merged} Android entries from {newest} (total {len(by_file)})")
PY
Loading