Skip to content

Add macOS nanoCLR host target scaffold#3256

Open
asp2286 wants to merge 9 commits intonanoframework:mainfrom
asp2286:feature/osx-host-skeleton
Open

Add macOS nanoCLR host target scaffold#3256
asp2286 wants to merge 9 commits intonanoframework:mainfrom
asp2286:feature/osx-host-skeleton

Conversation

@asp2286
Copy link
Copy Markdown

@asp2286 asp2286 commented Feb 14, 2026

Add macOS nanoCLR host target scaffold

Description

  • Adds a new isolated macOS host target under targets/osx.
  • Introduces a CMake + Ninja build entrypoint for macOS.
  • Provides minimal host glue aligned with the existing targets/win32 structure.
  • Includes a smoke executable (--help, --version) for build validation.
  • Adds a GitHub Actions workflow to build and run the smoke executable on macos-14.
  • Ensures all new C/C++ files comply with project clang-format rules.
  • Does not modify shared runtime behavior or existing targets.

Motivation and Context

This change introduces a safe and incremental starting point for a native macOS host nanoCLR implementation.

It enables:

  • Native macOS CI validation.
  • A structured landing zone for host portability work.
  • Incremental introduction of host abstraction and portability shims without destabilizing existing targets.

Resolves nanoframework/Home#1708

How Has This Been Tested?

  • Built locally on macOS (arm64) using CMake + Ninja.
  • Verified smoke executable runs and returns exit code 0 with --version.
  • Verified GitHub Actions workflow builds successfully on macos-14.
  • Confirmed clang-format compliance using project formatting rules.

Types of changes

  • New feature (non-breaking change which adds functionality to code)
  • Config and build (change in the configuration and build system, has no impact on code or features)
  • Improvement (non-breaking change that improves a feature, code or algorithm)
  • Bug fix (non-breaking change which fixes an issue with code or algorithm)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Dev Containers (changes related with Dev Containers, has no impact on code or features)
  • Dependencies/declarations (update dependencies or assembly declarations and changes associated, has no impact on code or features)
  • Documentation (changes or updates in the documentation, has no impact on code or features)

Checklist

  • My code follows the code style of this project (only if there are changes in source code).
  • My changes require an update to the documentation (there are changes that require the docs website to be updated).
  • I have updated the documentation accordingly (the changes require an update on the docs in this repo).
  • I have read the CONTRIBUTING document.
  • I have tested everything locally and all new and existing tests passed (only if there are changes in source code).

Summary by CodeRabbit

  • New Features

    • macOS host target for nanoCLR (arm64 & x86_64): lightweight launcher with smoke-mode, CLI (help/version/load), and host services (timing, events, memory heaps, file I/O, randomness, watchdog and minimal HAL/startup support).
  • Chores

    • Platform scaffolding and CMake integration, README documenting build/config/limitations, and CI updates (GitHub Actions workflow and pipeline entries to build and publish macOS artifacts).

- Adds targets/osx with a CMake+Ninja entrypoint
- Includes minimal host glue and smoke CLI behavior
- Adds GitHub Actions workflow for macOS build and smoke run
- No shared CLR runtime integration in this initial scaffold

Signed-off-by: Alexey Sokolov <asp2286@gmail.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a macOS host nanoCLR scaffold under targets/osx: CMake/Ninja build targets, a smoke-mode CLI executable, numerous platform HAL/PAL headers and stub implementations, a GitHub Actions macOS workflow, and documentation.

Changes

Cohort / File(s) Summary
CI & Top-level Build
.github/workflows/osx-nanoclr.yml, azure-pipelines.yml
Add GitHub Actions workflow for macos-14 and integrate an OSX build job into Azure Pipelines (build, smoke run, artifact upload).
CMake Entrypoints
targets/osx/CMakeLists.txt, targets/osx/nanoCLR/CMakeLists.txt
New macOS project and executable nanoFramework.nanoCLR; arch option (`arm64
Platform Identity & Basic Headers
targets/osx/Include/target_common.h, targets/osx/Include/target_platform.h, targets/osx/Include/target_board.h, targets/osx/Include/target_os.h
Add platform strings, include guards and TARGET_HAS_NANOBOOTER flag; minimal scaffolding headers.
HAL / PAL Headers
targets/osx/Include/targetHAL.h, targets/osx/Include/targetHAL_Time.h, targets/osx/Include/targetHAL_Power.h, targets/osx/Include/TargetHAL_Spi.h, targets/osx/Include/target_BlockStorage.h, targets/osx/Include/nanoHAL_Boot.h, targets/osx/Include/nanoHAL_Capabilites.h, targets/osx/Include/targetPAL_time.h
Introduce HAL/PAL declarations and macros: sleep macro, socket limit, trap, time/reset interfaces, SPI/blockstorage counts, capability/boot guards.
CLI, Startup & Options
targets/osx/nanoCLR/main.cpp, targets/osx/nanoCLR/CLRStartup.cpp, targets/osx/nanoCLR/ParseOptions_OSX.cpp, targets/osx/nanoCLR/platform_selector.h
Add smoke-mode CLI (--help, --version), stub ClrStartup/ClrExit/ClrReboot, options parser and PLATFORM_OSX_HOST macro.
Time & Timer PAL
targets/osx/nanoCLR/targetHAL_Time.cpp, targets/osx/nanoCLR/targetPAL_Time.cpp
Implement microsecond sleep and monotonic ticks (100‑ns units) and atomic next-compare storage/accessor.
Events & Synchronization
targets/osx/nanoCLR/targetPAL_Events.cpp
Platform event system with mutex/condvar: set/get/clear/masked-read, wait with timeout/infinite semantics, and detached bool timer thread.
Memory & Heap
targets/osx/nanoCLR/Memory.cpp, targets/osx/nanoCLR/platform_heap.cpp
Lazy-initialized default/custom heaps (10 MB each) via std::call_once; simple platform_malloc/platform_free.
File I/O & Block Storage
targets/osx/nanoCLR/FileStore_OSX.cpp, targets/osx/nanoCLR/Target_BlockStorage.cpp
Binary file load/save helpers with error reporting; block storage stubs (AddDevices, stream initialize returns false).
Debug, Utilities & Syscalls
targets/osx/nanoCLR/Target_HAL.cpp, targets/osx/nanoCLR/Various.cpp
Debug print trimming CR/LF, HAL/RTOS stubs (shutdown, global lock), perf ticks, sleep/yield, printf wrappers, stricmp, CPU power/shutdown stubs.
Random & Watchdog
targets/osx/nanoCLR/targetRandom.cpp, targets/osx/nanoCLR/WatchDog.cpp
Random utilities using std::random_device (u32, double, fill bytes); watchdog enable/disable/reset no-op stubs.
Docs
targets/osx/README.md
Documentation with build instructions, CMake options (NANO_OSX_ARCH, NANO_OSX_ENABLE_SMOKE), limitations and roadmap.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant GH as GitHub Actions
participant Runner as macOS Runner (macos-14)
participant CMake as CMake
participant Ninja as Ninja
participant nanoCLR as nanoFramework.nanoCLR
GH->>Runner: trigger build (push/PR on targets/osx/**)
Runner->>CMake: configure (CMAKE_OSX_ARCHITECTURES, SMOKE flag)
CMake->>Ninja: generate build files
Runner->>Ninja: build nanoFramework.nanoCLR
Runner->>nanoCLR: run nanoFramework.nanoCLR (emit version to smoke.log)
Runner->>GH: upload artifacts (binary, smoke.log)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #1708: Add macOS host nanoCLR target scaffold — This PR implements the targets/osx scaffold, CI workflow, smoke executable, and minimal HAL/PAL stubs described in the issue.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add macOS nanoCLR host target scaffold' accurately and concisely summarizes the main change—introducing a new macOS host target infrastructure.
Linked Issues check ✅ Passed All requirements from issue #1708 are met: targets/osx folder with CMakeLists.txt, README.md, Include/, nanoCLR/ subdirectories; CMake+Ninja build for arm64; smoke executable with --help/--version; GitHub Actions workflow; no changes to existing targets; clang-format compliance.
Out of Scope Changes check ✅ Passed All changes are scoped to the new macOS target. Only additions: targets/osx/** new files, .github/workflows/osx-nanoclr.yml workflow, and azure-pipelines.yml integration. No modifications to existing targets or shared runtime.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In @.github/workflows/osx-nanoclr.yml:
- Around line 36-38: The "Smoke Run" step runs
"./build/osx/bin/nanoFramework.nanoCLR --version | tee build/osx/smoke.log" but
the pipe hides a non-zero exit from nanoFramework.nanoCLR; change the step so
the shell uses pipefail (e.g., run the pipeline under bash with -o pipefail or
run "set -o pipefail" before the pipeline) or explicitly check PIPESTATUS[0]
after the tee and exit with that status; update the "Smoke Run" command
accordingly so the step fails when the smoke executable returns non-zero.

In `@targets/osx/Include/target_platform.h`:
- Around line 6-13: The header target_platform.h currently defines macros
NANOCLR_HOST_POSIX, NANOCLR_HOST_OSX, and NANOCLR_HOST_WINDOWS which are also
injected by the build via target_compile_definitions (CMakeLists), causing macro
redefinition warnings; fix this by removing the three `#define` lines for
NANOCLR_HOST_POSIX, NANOCLR_HOST_OSX and NANOCLR_HOST_WINDOWS from
target_platform.h and keep the include guard only (or alternatively remove the
corresponding target_compile_definitions in the CMakeLists if you prefer the
header as the single source of truth).

In `@targets/osx/nanoCLR/CLRStartup.cpp`:
- Around line 13-21: The local CLR_SETTINGS struct definition is missing the
conditional compilation used in the canonical nanoCLR_Application.h, causing a
layout mismatch; update the CLR_SETTINGS typedef to wrap the
PerformGarbageCollection and PerformHeapCompaction fields with the same
conditional compilation by surrounding those two fields with `#if`
defined(VIRTUAL_DEVICE) ... `#endif` so their presence matches the canonical
definition and prevents memory corruption when the shared runtime expects the
conditional layout.

In `@targets/osx/nanoCLR/FileStore_OSX.cpp`:
- Around line 15-23: The function (nanoCLR_OSX_LoadFile) currently reads into
content using std::istreambuf_iterator but doesn't detect I/O errors; after
performing content.assign(...) check the stream state (e.g., in.fail() or
in.bad()) and ensure the failure wasn't only EOF — if a non-EOF read error
occurred, set error to a descriptive message, clear/rollback content, and return
false; reference the ifstream variable "in" and the content.assign(...) call to
locate where to insert this post-read validation.

In `@targets/osx/nanoCLR/Memory.cpp`:
- Around line 32-34: The code sets SizeInBytes to c_DefaultHeapSizeBytes even
when allocation fails (s_MemoryStart == nullptr), which lets callers assume a
valid non-zero buffer; change the logic in the allocation path that assigns
BaseAddress = s_MemoryStart and SizeInBytes = static_cast<unsigned
int>(c_DefaultHeapSizeBytes) so that SizeInBytes is only set when s_MemoryStart
is non-null (otherwise set SizeInBytes to 0), and apply the same fix/pattern to
the CustomHeapLocation allocation path; update the assignments around
BaseAddress, SizeInBytes, s_MemoryStart and c_DefaultHeapSizeBytes accordingly.

In `@targets/osx/nanoCLR/platform_heap.cpp`:
- Around line 8-16: The OSX platform implementation of platform_malloc and
platform_free is missing the nanoHAL_v2.h declarations; add `#include`
<nanoHAL_v2.h> at the top of targets/osx/nanoCLR/platform_heap.cpp so the extern
"C" signatures for platform_malloc and platform_free match other platforms and
prevent linkage/signature mismatches when used across translation units.

In `@targets/osx/nanoCLR/targetHAL_Time.cpp`:
- Around line 21-28: In HAL_Time_CurrentSysTicks(), replace the use of
CLOCK_REALTIME with CLOCK_MONOTONIC when calling clock_gettime to obtain a
monotonic tick source; update the call to clock_gettime(CLOCK_MONOTONIC, &ts)
and keep the existing conversion of ts.tv_sec and ts.tv_nsec to .NET ticks (the
return expression) unchanged so the function continues to return 100ns units.

In `@targets/osx/nanoCLR/targetPAL_Events.cpp`:
- Around line 24-37: Events_SetBoolTimer currently writes *timerCompleteFlag
from a detached thread causing a data race; fix by treating the pointed bool as
an atomic and perform atomic stores instead of plain writes: after the nullptr
check use
std::atomic_store_explicit(reinterpret_cast<std::atomic<bool>*>(timerCompleteFlag),
false, std::memory_order_release) and in the detached thread use
std::atomic_store_explicit(reinterpret_cast<std::atomic<bool>*>(timerCompleteFlag),
true, std::memory_order_release); keep the function name Events_SetBoolTimer and
parameter timerCompleteFlag unchanged, and add a comment noting callers must
pass a bool that is actually an std::atomic<bool> (or update callers to use
std::atomic<bool>) if you cannot change the API.

In `@targets/osx/nanoCLR/targetPAL_Time.cpp`:
- Around line 14-18: The Time_SetCompare definition has C linkage in the header
but is implemented without extern "C"; update the implementation of
Time_SetCompare to use C linkage (e.g., add extern "C" for the function or wrap
it in an extern "C" block) so the definition matches the declaration in
nanoPAL_Time.h and prevents linkage mismatches for Time_SetCompare.

In `@targets/osx/nanoCLR/targetRandom.cpp`:
- Around line 15-19: The function nanoCLR_OSX_NextRandomDouble can return 1.0
when randomDevice() yields 0xFFFFFFFFU; change the scaling so the result is in
[0,1) by dividing by 2^32 (e.g. use 0x100000000ULL or cast to double and divide
by pow(2,32)) or use std::uniform_real_distribution<double>(0.0,
std::nextafter(1.0, 0.0)) to ensure the upper bound is exclusive; update
nanoCLR_OSX_NextRandomDouble to use one of these approaches so callers never
receive exactly 1.0.
🧹 Nitpick comments (10)
targets/osx/Include/targetHAL_Power.h (1)

11-14: CPU_Reset() always exits with success code 0.

If CPU_Reset() is ever called in response to a fault or error condition, std::exit(0) will signal success to the OS/CI, potentially masking failures. Consider accepting an exit code parameter or using a non-zero default. For the current smoke-test scaffold this is acceptable, but worth noting for future runtime integration.

targets/osx/nanoCLR/Target_HAL.cpp (1)

9-9: Parameter should be const char *.

szText is not modified by this function — it's only used to create a copy. The parameter type should be const char * for const-correctness. Additionally, the netcore target's DebugPrintCallback typedef expects const char *, making this change consistent with that contract.

Proposed fix
-void HAL_Windows_Debug_Print(char *szText)
+void HAL_Windows_Debug_Print(const char *szText)
targets/osx/nanoCLR/targetPAL_Time.cpp (1)

6-12: Missing include of the corresponding header targetPAL_time.h.

The AI summary mentions that targets/osx/Include/targetPAL_time.h declares the public API for these functions. Including it here would ensure the definitions match the declarations (return types, linkage, signatures) and catch mismatches at compile time.

 `#include` <atomic>
 `#include` <cstdint>
+#include "targetPAL_time.h"
.github/workflows/osx-nanoclr.yml (1)

3-11: Consider adding branch filters to avoid running on every push to every branch.

Currently the workflow triggers on push to any branch that touches targets/osx/**. If contributors push frequently to feature branches, this could consume CI minutes. Consider restricting push triggers to specific branches (e.g., main, develop), while keeping the pull_request trigger broad.

targets/osx/nanoCLR/CLRStartup.cpp (1)

31-36: ClrStartup takes the struct by value — consider taking by const reference or pointer.

This matches the pattern used in other targets, but worth verifying. If the canonical signature uses a pointer or reference, this mismatch would cause link errors later.

targets/osx/README.md (2)

7-7: Replace PR-centric language with permanent documentation phrasing.

Lines 7, 13, 28, etc. use "This PR provides…" / "This PR:" which becomes confusing after merge. Consider rephrasing to "This target provides…" or "This scaffold includes…".


41-43: Wrap build commands in fenced code blocks.

The build commands aren't inside ``` fences, so they render as plain paragraphs rather than copyable command blocks in most Markdown renderers.

Proposed fix
-cmake -S targets/osx -B build/osx -G Ninja -DNANO_OSX_ARCH=arm64 -DNANO_OSX_ENABLE_SMOKE=ON
-cmake --build build/osx
-./build/osx/bin/nanoFramework.nanoCLR --version
+```bash
+cmake -S targets/osx -B build/osx -G Ninja -DNANO_OSX_ARCH=arm64 -DNANO_OSX_ENABLE_SMOKE=ON
+cmake --build build/osx
+./build/osx/bin/nanoFramework.nanoCLR --version
+```
targets/osx/nanoCLR/Various.cpp (2)

18-29: Global lock stubs are internally inconsistent.

HAL_Windows_AcquireGlobalLock() is a no-op, yet HAL_Windows_HasGlobalLock() always returns false. If any caller acquires and then checks, the invariant is violated. Fine for a scaffold with no runtime wired, but worth a // TODO so this isn't forgotten when the runtime is integrated.


77-99: Use POSIX strcasecmp instead of hand-rolling case-insensitive compare.

macOS provides strcasecmp in <strings.h>, which handles locale-aware comparison and is well-tested. The manual loop is unnecessary on a POSIX platform.

Proposed fix
+#include <strings.h>
+
 int hal_stricmp(const char *dst, const char *src)
 {
-    int f = 0;
-    int l = 0;
-
-    do
-    {
-        f = static_cast<unsigned char>(*(dst++));
-        l = static_cast<unsigned char>(*(src++));
-
-        if (f >= 'A' && f <= 'Z')
-        {
-            f -= 'A' - 'a';
-        }
-
-        if (l >= 'A' && l <= 'Z')
-        {
-            l -= 'A' - 'a';
-        }
-    } while (f && (f == l));
-
-    return f - l;
+    return strcasecmp(dst, src);
 }
targets/osx/nanoCLR/targetPAL_Events.cpp (1)

87-110: Events_WaitForEvents returns matched bits without clearing them.

Events_Get clears the matched bits from s_systemEvents, but Events_WaitForEvents only reads them. If callers expect wait-then-consume semantics, those events will fire repeatedly. If this is intentional (peek semantics, caller uses Events_Get afterward), a brief comment would clarify the contract.

- Make CI smoke step fail on non-zero exit (pipefail)
- Remove redundant host macro defines from target_platform.h
- Align CLR_SETTINGS layout with canonical definition
- Improve file read error handling and heap allocation reporting
- Use monotonic clock for sys ticks and fix C linkage for Time_SetCompare
- Ensure random double is in [0,1) and apply minor docs/const-correctness fixes

Signed-off-by: Alexey Sokolov <asp2286@gmail.com>
- Align Events_SetBoolTimer behavior with Win32 assumption of a single active timer
- Prevent earlier timers from completing after a newer one is scheduled (generation cancel)
- Keep implementation minimal as a scaffold placeholder until completion queue wiring

Signed-off-by: Alexey Sokolov <asp2286@gmail.com>
@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 15, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 15, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@targets/osx/Include/nanoHAL_Capabilites.h`:
- Around line 1-9: The filename "nanoHAL_Capabilites.h" is misspelled (missing
'i') and mismatches the include guard NANOHAL_CAPABILITIES_H; rename all four
files named nanoHAL_Capabilites.h to nanoHAL_Capabilities.h (locations:
src/HAL/Include, src/HAL, targets/osx/Include, targets/win32/Include) and update
the three includes that reference the old name in src/HAL/nanoHAL_Capabilites.c,
src/HAL/Include/nanoHAL_v2.h, and src/HAL/nanoHAL_Boot.c to `#include`
"nanoHAL_Capabilities.h" so the filename matches the existing guard and all
references.
🧹 Nitpick comments (6)
targets/osx/nanoCLR/targetPAL_Events.cpp (1)

87-95: notify_all() after clearing events is unnecessary.

Waiters in Events_WaitForEvents are predicated on events being set, not cleared. Notifying after a clear only causes spurious wakeups — the predicate will re-evaluate and the waiters will go back to sleep. Harmless, but wasteful.

♻️ Suggested simplification
 void Events_Clear(uint32_t events)
 {
-    {
-        std::lock_guard<std::mutex> lock(s_eventsMutex);
-        s_systemEvents &= ~events;
-    }
-
-    s_eventsCondition.notify_all();
+    std::lock_guard<std::mutex> lock(s_eventsMutex);
+    s_systemEvents &= ~events;
 }
targets/osx/nanoCLR/targetRandom.cpp (1)

21-33: FillRandomBytes discards 24 bits of entropy per iteration.

Each call to randomDevice() produces 32 bits but only 8 are used. Fine for a scaffold, but when this moves beyond placeholder status, consider filling 4 bytes per call.

♻️ Optional optimization for later
 void nanoCLR_OSX_FillRandomBytes(uint8_t *buffer, unsigned int count)
 {
     if (buffer == nullptr)
     {
         return;
     }
 
     static std::random_device randomDevice;
 
-    for (unsigned int i = 0; i < count; i++)
+    unsigned int i = 0;
+    for (; i + 4 <= count; i += 4)
     {
-        buffer[i] = static_cast<uint8_t>(randomDevice());
+        uint32_t val = randomDevice();
+        std::memcpy(&buffer[i], &val, 4);
+    }
+    if (i < count)
+    {
+        uint32_t val = randomDevice();
+        std::memcpy(&buffer[i], &val, count - i);
     }
 }
targets/osx/Include/targetHAL_Power.h (1)

11-19: CPU_IsSoftRebootSupported() returns true but CPU_Reset() terminates the process.

If a caller checks CPU_IsSoftRebootSupported() and then calls CPU_Reset() expecting a soft restart, it will instead get a hard exit. This is fine for the current scaffold scope, but worth a TODO so it isn't forgotten when wiring in the runtime.

targets/osx/nanoCLR/main.cpp (1)

27-43: main.cpp duplicates and diverges from ParseOptions_OSX.cpp argument parsing.

main.cpp rolls its own argument loop while ParseOptions_OSX.cpp provides a dedicated parser (nanoCLR_OSX_ParseOptions) that also handles -load and reports errors for unknown flags. Here, unknown arguments are silently ignored. Consider wiring the dedicated parser into main to keep behaviour consistent and avoid maintaining two parallel parsing paths.

targets/osx/nanoCLR/ParseOptions_OSX.cpp (1)

9-14: NanoClrHostOptions and nanoCLR_OSX_ParseOptions lack a shared header.

The struct and function are defined entirely in this .cpp file with no corresponding header, so no other translation unit (e.g., main.cpp) can reference them without redeclaring the types. Consider extracting the struct and function declaration into a header (e.g., ParseOptions_OSX.h) so the parser can actually be consumed by the launcher.

Also applies to: 17-20

targets/osx/nanoCLR/CLRStartup.cpp (1)

13-23: Local CLR_SETTINGS typedef duplicates the canonical definition — track parity.

This is a verbatim copy of the struct from nanoCLR_Application.h. If fields are added or reordered upstream, silent layout mismatches will cause memory corruption at integration time. The TODO on lines 8-9 already acknowledges this, but consider adding a compile-time guard (e.g., a static_assert on sizeof) once the shared header becomes available, to catch drift early.

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 15, 2026

Walkthrough

Adds a macOS host target scaffold under targets/osx: CMake/Ninja build entrypoints, a GitHub Actions macOS workflow, platform HAL/PAL headers, and numerous stubbed nanoCLR host sources (memory, time, events, startup, I/O, utilities) plus a smoke executable and README.

Changes

Cohort / File(s) Summary
CI & Build Entrypoints
.github/workflows/osx-nanoclr.yml, targets/osx/CMakeLists.txt, targets/osx/nanoCLR/CMakeLists.txt Add macOS GitHub Actions workflow and CMake/Ninja entrypoints; arch option (arm64/x86_64), NANO_OSX_ENABLE_SMOKE toggle, and new nanoFramework.nanoCLR executable target.
Platform Identity & Common Headers
targets/osx/Include/target_common.h, targets/osx/Include/target_platform.h, targets/osx/Include/target_board.h, targets/osx/Include/target_os.h Add platform/name strings, include guards and TARGET_HAS_NANOBOOTER flag; minimal platform scaffolding.
HAL / Target Headers
targets/osx/Include/targetHAL.h, targets/osx/Include/targetHAL_Time.h, targets/osx/Include/targetHAL_Power.h, targets/osx/Include/TargetHAL_Spi.h, targets/osx/Include/target_BlockStorage.h, targets/osx/Include/nanoHAL_Boot.h, targets/osx/Include/nanoHAL_Capabilites.h, targets/osx/Include/targetPAL_time.h Introduce HAL/PAL headers: sleep macro, socket limit, trap macro, time declarations, reset/soft-reboot stubs, SPI/block storage counts, and capability/boot guards.
Startup, CLI & Options
targets/osx/nanoCLR/CLRStartup.cpp, targets/osx/nanoCLR/main.cpp, targets/osx/nanoCLR/ParseOptions_OSX.cpp, targets/osx/nanoCLR/platform_selector.h Add CLR_SETTINGS and stubbed ClrStartup/ClrExit/ClrReboot; smoke-mode CLI (--help/--version); options parser (-load) and PLATFORM_OSX_HOST macro.
Time & Timer PAL
targets/osx/nanoCLR/targetHAL_Time.cpp, targets/osx/nanoCLR/targetPAL_Time.cpp Implement microsecond sleep and monotonic ticks (returns 100‑ns units); atomic next-compare storage and accessor for scheduling.
Events & Synchronization
targets/osx/nanoCLR/targetPAL_Events.cpp Platform event system with mutex/condvar: set/get/clear/masked-read, wait with timeout/infinite semantics, and detached boolean timer thread.
Memory & Heap
targets/osx/nanoCLR/Memory.cpp, targets/osx/nanoCLR/platform_heap.cpp Lazy-initialized default and custom heaps (10 MB each) via std::call_once; simple platform_malloc/platform_free wrappers.
File I/O & Block Storage
targets/osx/nanoCLR/FileStore_OSX.cpp, targets/osx/nanoCLR/Target_BlockStorage.cpp Binary file load/save helpers with error reporting; block storage stubs (AddDevices, stream initialize returns false).
Debug, Utilities & Syscalls
targets/osx/nanoCLR/Target_HAL.cpp, targets/osx/nanoCLR/Various.cpp Debug print (trim CR/LF), HAL/RTOS stubs (shutdown checks, global lock), perf ticks, sleep/yield, printf wrappers, stricmp, CPU power/shutdown stubs.
Random & Watchdog
targets/osx/nanoCLR/targetRandom.cpp, targets/osx/nanoCLR/WatchDog.cpp Random utilities using std::random_device (u32, double, fill bytes); watchdog enable/disable/reset implemented as no-op stubs.
Docs
targets/osx/README.md Documentation with build instructions, CMake options (NANO_OSX_ARCH, NANO_OSX_ENABLE_SMOKE), limitations and roadmap.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #1708: Add macOS host nanoCLR target scaffold — This PR implements the targets/osx scaffold, CI workflow, smoke executable, and minimal HAL/PAL stubs described in the issue.

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Comment @coderabbitai help to get the list of available commands and usage tips.

Thanks for the review!

I’ve added brief comments clarifying the intent and temporary scaffold nature of the macOS host implementation. The focus here is readability and parity with existing host targets rather than meeting a global coverage threshold.

Happy to expand documentation further if needed.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 15, 2026

Tip

For best results, initiate chat on the files or code changes.

@asp2286, thanks for the clarification! Your approach makes perfect sense for an initial scaffold PR. Stub implementations and minimal host glue naturally have lower docstring density, and aligning with the existing targets/win32 pattern is the right call for consistency.

The docstring coverage check is helpful for production code, but for a foundational scaffold like this where the focus is on buildable infrastructure and CI validation, inline comments and the README provide sufficient context.

Feel free to ping me if you'd like a closer look at any specific area as you wire in the shared runtime in future PRs. Looking forward to seeing this evolve! 🚀


🧠 Learnings used
Learnt from: asp2286
Repo: nanoframework/nf-interpreter PR: 3256
File: targets/osx/nanoCLR/targetPAL_Events.cpp:27-53
Timestamp: 2026-02-15T06:57:34.708Z
Learning: The Win32 and macOS host targets in `Events_SetBoolTimer` use `bool*` for timer callbacks without atomic synchronization, mirroring the existing HAL contract behavior. This is a known pattern that will be refactored when wiring into a proper completion/event loop in future PRs.

@Ellerbach
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

- Add BUILD_OSX gating in TargetsToBuild based on targets/osx changes

- Add Build_OSX_nanoCLR job on macos-14 running CMake+Ninja smoke build and --version

- Keep job path-filtered to avoid impacting unrelated PRs

Signed-off-by: Alexey Sokolov <asp2286@gmail.com>
@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 16, 2026

/azp run

@Ellerbach Thanks for triggering the Azure pipeline.

Looking ahead, would it make sense to add a path-filtered Build_OSX_nanoCLR job to Azure DevOps as well, so the macOS host target stays validated in the primary CI gate as it evolves?
Would you prefer that to be included in this PR, or submitted as a small follow-up?

Happy to align with the project direction.

@Ellerbach
Copy link
Copy Markdown
Member

Looking ahead, would it make sense to add a path-filtered Build_OSX_nanoCLR job to Azure DevOps as well, so the macOS host target stays validated in the primary CI gate as it evolves?
Would you prefer that to be included in this PR, or submitted as a small follow-up?

Good question. I think we can find a compromise so that it does not run all the time but runs like some of the devices time to time. Reason is that it's a bit costly to run it all the time. And there are high chances if now one touches, it should be fine. So, as a gate but not all the time. Does that makes sense?

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 16, 2026

Looking ahead, would it make sense to add a path-filtered Build_OSX_nanoCLR job to Azure DevOps as well, so the macOS host target stays validated in the primary CI gate as it evolves?
Would you prefer that to be included in this PR, or submitted as a small follow-up?

Good question. I think we can find a compromise so that it does not run all the time but runs like some of the devices time to time. Reason is that it's a bit costly to run it all the time. And there are high chances if now one touches, it should be fine. So, as a gate but not all the time. Does that makes sense?

Thanks for the clarification — that makes sense 👍

I've added a path-filtered Build_OSX_nanoCLR job so the macOS target is validated when touched, without increasing CI cost for unrelated changes.

If over time we see shared changes affecting it, we can consider widening the filter or adding a scheduled validation run.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@azure-pipelines.yml`:
- Around line 1047-1055: The Build_OSX_nanoCLR job's condition must be aligned
with other target jobs: add a guard to skip when SKIP_BUILD is true (or
StartReleaseCandidate implies skipping), honour BUILD_ALL=true, and allow a
manual trigger via the BUILD_OSX__ variable. Update the job's condition (the
existing condition referencing
dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX']) to also
check ne(variables['SKIP_BUILD'],'true') (or equivalent StartReleaseCandidate
guard), include an OR branch that accepts eq(variables['BUILD_ALL'],'true'), and
accept eq(variables['BUILD_OSX__'],'true') for manual builds so it matches the
logic used by Build_WIN32_nanoCLR and other targets. Ensure dependsOn remains or
is adjusted to include Check_Build_Options like the other jobs.
- Around line 346-358: The Check_Code_Style job's condition is missing the
BUILD_OSX flag so OSX-only changes skip linting; update the Check_Code_Style
condition to include
eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true)
alongside the other target checks, then add Check_Code_Style to the
Build_OSX_nanoCLR job's dependsOn and wrap Build_OSX_nanoCLR's existing
condition with succeeded('Check_Code_Style') so the OSX build waits for and
respects the style check result.
- Around line 1063-1073: Add an explicit validation after running
./build/osx/bin/nanoFramework.nanoCLR --version: capture its stdout (from the
tee'd build/osx/smoke.log) and grep/assert it contains an expected marker (e.g.,
"nanoCLR" or a semantic version regex) and exit non‑zero if the check fails so
the pipeline fails on unexpected output; additionally, avoid an unpinned
Homebrew install by installing a specific formula/version or switching to a
reproducible installer for ninja (replace the plain brew install ninja
invocation) — update the bash block that runs cmake/cmake --build and the final
--version check to include these assertions and the pinned ninja install
approach.
🧹 Nitpick comments (2)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@azure-pipelines.yml`:
- Around line 346-358: The Check_Code_Style job's condition is missing the
BUILD_OSX flag so OSX-only changes skip linting; update the Check_Code_Style
condition to include
eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true)
alongside the other target checks, then add Check_Code_Style to the
Build_OSX_nanoCLR job's dependsOn and wrap Build_OSX_nanoCLR's existing
condition with succeeded('Check_Code_Style') so the OSX build waits for and
respects the style check result.
- Around line 1063-1073: Add an explicit validation after running
./build/osx/bin/nanoFramework.nanoCLR --version: capture its stdout (from the
tee'd build/osx/smoke.log) and grep/assert it contains an expected marker (e.g.,
"nanoCLR" or a semantic version regex) and exit non‑zero if the check fails so
the pipeline fails on unexpected output; additionally, avoid an unpinned
Homebrew install by installing a specific formula/version or switching to a
reproducible installer for ninja (replace the plain brew install ninja
invocation) — update the bash block that runs cmake/cmake --build and the final
--version check to include these assertions and the pinned ninja install
approach.
azure-pipelines.yml (2)

346-358: Check_Code_Style condition does not include BUILD_OSX — OSX-only PRs will skip style checks.

Every other target flag (BUILD_CHIBIOS, BUILD_ESP32, …, BUILD_WIN32) gates style checking; BUILD_OSX is absent, so PRs touching only targets/osx will never run check-code-style. This may be acceptable during the scaffold phase, but worth tracking as a follow-up so new C/C++ files under targets/osx get the same linting gate.

Proposed fix
       or(
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_ALL'], true),
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_CHIBIOS'], true),
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_ESP32'], true),
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_FREERTOS'], true),
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_TI'], true),
         eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_AZURERTOS'], true),
-        eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_WIN32'], true)
+        eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_WIN32'], true),
+        eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true)
       )

If added, also add Check_Code_Style to Build_OSX_nanoCLR's dependsOn and wrap its condition with succeeded('Check_Code_Style') like the other jobs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@azure-pipelines.yml` around lines 346 - 358, The Check_Code_Style job's
condition is missing the BUILD_OSX flag so OSX-only changes skip linting; update
the Check_Code_Style condition to include
eq(dependencies.Check_Build_Options.outputs['TargetsToBuild.BUILD_OSX'], true)
alongside the other target checks, then add Check_Code_Style to the
Build_OSX_nanoCLR job's dependsOn and wrap Build_OSX_nanoCLR's existing
condition with succeeded('Check_Code_Style') so the OSX build waits for and
respects the style check result.

1063-1073: Build step looks solid; consider validating the exit code explicitly.

set -euo pipefail is good — any non-zero exit will fail the step. One minor hardening note: if the --version output is empty or unexpected, the step still passes. You could add a simple grep to assert the output contains the expected version string.

Also, brew install ninja is not pinned and could break if Homebrew updates Ninja to an incompatible version. This is low-risk for now but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@azure-pipelines.yml` around lines 1063 - 1073, Add an explicit validation
after running ./build/osx/bin/nanoFramework.nanoCLR --version: capture its
stdout (from the tee'd build/osx/smoke.log) and grep/assert it contains an
expected marker (e.g., "nanoCLR" or a semantic version regex) and exit non‑zero
if the check fails so the pipeline fails on unexpected output; additionally,
avoid an unpinned Homebrew install by installing a specific formula/version or
switching to a reproducible installer for ninja (replace the plain brew install
ninja invocation) — update the bash block that runs cmake/cmake --build and the
final --version check to include these assertions and the pinned ninja install
approach.

- Make Build_OSX_nanoCLR respect SKIP_BUILD, BUILD_ALL, and manual triggers

- Ensure Check_Code_Style runs for OSX-only changes and OSX job depends on it

- Add basic smoke output validation

Signed-off-by: Alexey Sokolov <asp2286@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@azure-pipelines.yml`:
- Line 1076: The checkout step currently uses "checkout: self" without a shallow
clone; update that step by adding "fetchDepth: 1" under the "checkout: self"
entry (maintain the same YAML indentation) so the pipeline performs a shallow
clone like the other jobs; ensure no other checkout options are removed and that
the new "fetchDepth: 1" line is aligned with existing checkout properties.
🧹 Nitpick comments (1)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@azure-pipelines.yml`:
- Line 1076: The checkout step currently uses "checkout: self" without a shallow
clone; update that step by adding "fetchDepth: 1" under the "checkout: self"
entry (maintain the same YAML indentation) so the pipeline performs a shallow
clone like the other jobs; ensure no other checkout options are removed and that
the new "fetchDepth: 1" line is aligned with existing checkout properties.
azure-pipelines.yml (1)

1076-1076: Consider adding fetchDepth: 1 to the checkout step.

Other jobs in this pipeline (e.g., Check_Code_Style at line 370, Run_UnitTests_mscorlib at line 1327) use fetchDepth: 1 to speed up checkout. Since this job only needs a shallow clone for the build, the same optimization applies here.

Proposed fix
     steps:
       - checkout: self
+        fetchDepth: 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@azure-pipelines.yml` at line 1076, The checkout step currently uses
"checkout: self" without a shallow clone; update that step by adding
"fetchDepth: 1" under the "checkout: self" entry (maintain the same YAML
indentation) so the pipeline performs a shallow clone like the other jobs;
ensure no other checkout options are removed and that the new "fetchDepth: 1"
line is aligned with existing checkout properties.

@Ellerbach
Copy link
Copy Markdown
Member

Thanks for the clarification — that makes sense 👍

I've added a path-filtered Build_OSX_nanoCLR job so the macOS target is validated when touched, without increasing CI cost for unrelated changes.

If over time we see shared changes affecting it, we can consider widening the filter or adding a scheduled validation run.

Thanks, yes, let's do that!

@Ellerbach
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 17, 2026

/azp run

@Ellerbach thanks again for triggering the Azure pipeline.

I was able to reproduce the failure locally.
The initial build was forcing -DNANO_OSX_ARCH=arm64, which causes a Bad CPU type in executable error on Intel macOS agents.

I validated the fix on:

  • Apple Silicon (arm64) – builds and runs successfully.
  • Intel macOS (x86_64) – reproduced the failure and confirmed the dynamic arch detection resolves it.

The pipeline now detects the agent architecture via uname -m and configures NANO_OSX_ARCH accordingly.

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 18, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Commenter does not have sufficient privileges for PR 3256 in repo nanoframework/nf-interpreter

@josesimoes
Copy link
Copy Markdown
Member

Hi @asp2286,

Appreciate your work on putting up a MacOS target. As you probably know we have the nanoCLR distributed as nanoclr .NET tool. Have you tested this to run on MacOS?

I'm asking because that's the target we've been using for quite some time to run a virtual device and that is used by the test framework too. There is an old WIN32 target of which the new nanoclr shares files with. The C# wrapper interacts with it through Interop.

On a quick look at the PR, I got the impression that you're mixing old (like command parsing) and the new architecture.

If the existing nanoclr tool does not run on MacOS, I would rather have "fixed" whatever is needed in the existing project(s) instead of creating another target.

Don't want to sound like I'm dismissing your work and effort. Just looking at this from a broader perspective. That's one of the reasons we have in the contributor guideline a note asking developers to discuss with the core team breaking or big workloads before committing to an approach. No one likes to waste time and effort! 😉

@josesimoes
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 18, 2026

Hi @josesimoes — thanks for the thoughtful feedback, totally fair questions.

I did look into the existing nanoclr tool path (targets/netcore). Today it is Windows-scoped end-to-end:

  • CLI/Host use kernel32 (SetDllDirectory, FreeLibrary) and are marked Windows-only.
  • The native backend is built via MSVC *.vcxproj and compiles a lot of targets/win32/* host glue (WinAPI time/memory/files, etc.).
  • The tool packaging/update flow also assumes Windows artifacts (e.g. WIN_DLL_nanoCLR) and a single any/NanoCLR layout.

So at the moment nanoclr --help can’t run on macOS and the native backend doesn’t have a macOS build path.

My intent with targets/osx was to provide a minimal native host scaffold + CI plumbing, as an enabling step for macOS support. I agree the long-term goal should be to converge on the nanoclr tool + Host interop path (single contract), not to maintain two divergent hosts.

If the preferred direction is: “make targets/netcore backend cross-platform (CMake + portable shims) and ship per-RID native assets”, I’m happy to pivot and align follow-up work accordingly.

@asp2286
Copy link
Copy Markdown
Author

asp2286 commented Feb 18, 2026

Just to clarify one architectural point:

I don’t see targets/osx as an alternative host or parallel product.
The intention is to use it as an incubator for portability shims (time/memory/events/files) and a CMake-based build path that would eventually be needed for making the targets/netcore backend cross-platform.

In other words, the POSIX replacements and CMake plumbing can later be moved into the netcore backend during the actual porting effort, so we converge rather than diverge.

@josesimoes
Copy link
Copy Markdown
Member

From your comments it's clear that you've looked deep into the code base.
Yes, the convergence to a single target netcore would be the ultimate goal for this. In the process, the work done would also take us closer to have *nix builds.

A couple of thoughts:

  1. The reason for using MSVC is mostly historical. The original port was an emulator and linked directly to C/C++ code files from the CLR. As the project was already there, it felt easier and smooth to "just" keep working and evolving it. At some point, in an effort to modernize it and open up the possibility to run this on other platforms and allow other Wire Protocol transport layers, the .NET CLI was introduced. The test framework started using it and, to be honest, the WIN32 solution only remains there by lack of time to do the final migration of the code files to netcore and get rid of it completely.
  2. As all the other targets that run on an MCU, CMake is the build system. With VS fully supporting it, it can easily be integrated in the .NET solution and move forward with this.
  3. Understood your notes, comments and reasoning so I suppose we're reasonably aligned by now.

To wrap up this: let's make targets/netcore backend cross-platform (CMake + portable shims) and ship per-RID native assets.

asp2286 added 3 commits March 23, 2026 12:34
…oader

The nanoCLR host relied on kernel32 SetDllDirectory/FreeLibrary P/Invokes
that only work on Windows. Replace them with a cross-platform resolver
built on .NET's NativeLibrary API that probes standard paths on Windows,
macOS, and Linux using consistent library naming without the lib prefix.
# Conflicts:
#	azure-pipelines.yml
…wrapper

The C# layer (targets/netcore) owns all CLI argument parsing via CommandLineParser.
The native host only needs to export P/Invoke entry points (nanoCLR_Run,
nanoCLR_LoadAssembly, nanoCLR_GetVersion, etc.) — it must not duplicate parsing.

- Delete targets/osx/nanoCLR/ParseOptions_OSX.cpp
- Remove ParseOptions_OSX.cpp from targets/osx/nanoCLR/CMakeLists.txt
- Remove NanoClrHostOptions struct and nanoCLR_OSX_ParseOptions declaration/call sites
@asp2286 asp2286 requested a review from josesimoes March 23, 2026 06:07
@josesimoes
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Copy Markdown
Member

@josesimoes josesimoes left a comment

Choose a reason for hiding this comment

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

Looking good.
I need to test the part related with HHD interop DLL loading. I recall that one is very picky...

Apart from that, looking good.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new macOS host target scaffold under targets/osx and updates CI/build plumbing to compile and smoke-run it, alongside improving the .NET host’s native-library loading to be cross-platform friendly.

Changes:

  • Introduces targets/osx CMake/Ninja build with a minimal nanoCLR host “smoke” executable and placeholder PAL/HAL glue.
  • Adds CI coverage for macOS (GitHub Actions workflow + Azure Pipelines job/target-selection updates).
  • Reworks .NET host native-library loading/unloading to use a DllImportResolver (removing kernel32-only logic) and adjusts CLI startup path resolution.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
targets/osx/README.md Documents macOS target scaffold, build instructions, and limitations.
targets/osx/CMakeLists.txt Adds macOS-only CMake entrypoint and arch/smoke configuration.
targets/osx/nanoCLR/CMakeLists.txt Defines macOS nanoCLR build target, includes, and compile defs.
targets/osx/nanoCLR/main.cpp Smoke-mode entrypoint printing banner and scaffold status.
targets/osx/nanoCLR/CLRStartup.cpp Stub CLR startup API surface for future runtime integration.
targets/osx/nanoCLR/Various.cpp Placeholder host implementations for timing/printf/power/yield APIs.
targets/osx/nanoCLR/Target_HAL.cpp Provides host debug print implementation.
targets/osx/nanoCLR/targetHAL_Time.cpp Implements monotonic time and microsecond sleeps for macOS.
targets/osx/nanoCLR/targetPAL_Time.cpp Placeholder compare-timer storage for future completion wiring.
targets/osx/nanoCLR/targetPAL_Events.cpp Minimal event system with placeholder bool-timer support.
targets/osx/nanoCLR/targetRandom.cpp Provides host randomness helpers.
targets/osx/nanoCLR/Memory.cpp Allocates/fills placeholder heaps for host scaffold.
targets/osx/nanoCLR/platform_heap.cpp Provides platform_malloc/free for host builds.
targets/osx/nanoCLR/Target_BlockStorage.cpp Placeholder block storage hooks for host mode.
targets/osx/nanoCLR/FileStore_OSX.cpp Minimal host file load/save helpers (scaffold).
targets/osx/nanoCLR/WatchDog.cpp Stub watchdog functions for host scaffold.
targets/osx/nanoCLR/platform_selector.h Defines macOS platform selector macro(s).
targets/osx/Include/targetHAL.h Defines macOS target HAL macros/capabilities/time conversions.
targets/osx/Include/targetHAL_Time.h Declares macOS HAL time functions.
targets/osx/Include/targetHAL_Power.h Defines CPU reset/soft reboot support for host scaffold.
targets/osx/Include/TargetHAL_Spi.h Defines SPI bus/device constants (placeholder).
targets/osx/Include/targetPAL_time.h Adds macOS target PAL time header (matches win32 shape).
targets/osx/Include/target_platform.h Adds macOS target platform header (placeholder).
targets/osx/Include/target_os.h Declares target OS macros (no nanobooter).
targets/osx/Include/target_common.h Declares target/platform info strings for macOS.
targets/osx/Include/target_board.h Adds macOS target board header (placeholder).
targets/osx/Include/target_BlockStorage.h Declares block storage count for macOS target.
targets/osx/Include/nanoHAL_Capabilites.h Adds capabilities header required by shared includes (empty placeholder).
targets/osx/Include/nanoHAL_Boot.h Adds boot header required by shared includes (empty placeholder).
.github/workflows/osx-nanoclr.yml Adds GitHub Actions workflow to build + smoke-run on macos-14.
azure-pipelines.yml Adds BUILD_OSX detection + macOS build/smoke job to Azure Pipelines.
targets/netcore/nanoFramework.nanoCLR.Host/Interop/NativeNanoClrLoader.cs Adds cross-platform native library probing via DllImportResolver.
targets/netcore/nanoFramework.nanoCLR.Host/Interop/nanoCLR.cs Switches to the new loader; removes kernel32 SetDllDirectory/FreeLibrary logic.
targets/netcore/nanoFramework.nanoCLR.CLI/Program.cs Simplifies executing-path computation; removes kernel32 SetDllDirectory usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +28
void PrintBanner()
{
std::cout << ".NET nanoFramework nanoCLR OSX v" << NANOCLR_OSX_VERSION_STRING << "\n";
std::cout << "Copyright (c) .NET Foundation and Contributors\n\n";
}
} // namespace

int main()
{
PrintBanner();

#if NANO_OSX_ENABLE_SMOKE
// TODO: Replace with real CLR startup flow once shared runtime portability shims are in place.
std::cout << "Smoke mode is enabled. Runtime startup is not wired yet.\n";
return 0;
#else
std::cerr << "Smoke mode is disabled and runtime startup is not available yet.\n";
return 2;
#endif
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

main() ignores command-line arguments, but the README/CI invocation implies support for --help and --version. Consider switching to int main(int argc, char** argv) and implementing minimal parsing so --help/--version produce deterministic output (and optionally exit early) instead of always printing the smoke banner/message.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +16
void HAL_Windows_Debug_Print(const char *szText)
{
if (szText == nullptr)
{
return;
}

std::string text(szText);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

HAL_Windows_Debug_Print is declared as void HAL_Windows_Debug_Print(char* szText) in the shared header (src/CLR/Include/nanoCLR_Win32.h). Defining it here as const char* will produce a different mangled symbol once shared runtime code is wired for macOS, leading to unresolved externals. Align the signature with the existing declaration (you can still treat the buffer as read-only internally).

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +140
$"The macOS native library is not available. " +
$"Build it from targets/osx and place it alongside the tool, " +
$"or set the search directory explicitly via NativeNanoClrLoader.");
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

On macOS, the loader throws a DllNotFoundException suggesting the native library can be built from targets/osx, but this PR’s CMake target in targets/osx currently builds an executable (nanoFramework.nanoCLR), not a nanoFramework.nanoCLR.dylib. Either update the guidance (and/or the expected filename) to match the actual artifact produced today, or change the macOS build to produce the dylib that the host expects to load.

Suggested change
$"The macOS native library is not available. " +
$"Build it from targets/osx and place it alongside the tool, " +
$"or set the search directory explicitly via NativeNanoClrLoader.");
"The macOS native nanoFramework.nanoCLR library could not be found or loaded. " +
"Ensure that the correct macOS native library is built and available on the native library search path, " +
"or set the search directory explicitly via NativeNanoClrLoader.");

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +215
private static string NormalizeDirectory(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return string.Empty;
}

string fullPath = Path.GetFullPath(path);
string rootPath = Path.GetPathRoot(fullPath) ?? string.Empty;

if (string.Equals(fullPath, rootPath, StringComparison.Ordinal))
{
return fullPath;
}

return fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

NormalizeDirectory() calls Path.GetFullPath(path) without handling exceptions. If the caller passes an invalid/unresolvable path (e.g., bad characters, missing drive), this will throw during loader initialization and can mask the underlying native-load problem. Consider catching Exception types thrown by GetFullPath and returning an empty string or rethrowing a more actionable argument exception.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +53
void Events_SetBoolTimer(bool *timerCompleteFlag, uint32_t millisecondsFromNow)
{
// TODO: Placeholder timer implementation. Make this path thread-safe when runtime integration is wired.
if (timerCompleteFlag == nullptr)
{
return;
}

s_savedTimerCompleteFlag = timerCompleteFlag;
*timerCompleteFlag = false;
const uint64_t myGen = s_boolTimerGeneration.fetch_add(1, std::memory_order_acq_rel) + 1;

std::thread([millisecondsFromNow, myGen]() {
std::this_thread::sleep_for(std::chrono::milliseconds(millisecondsFromNow));

if (s_boolTimerGeneration.load(std::memory_order_acquire) != myGen)
{
return;
}

if (s_savedTimerCompleteFlag != nullptr)
{
// TODO: Scaffold placeholder until this is wired into a proper completion/event path.
*s_savedTimerCompleteFlag = true;
}
}).detach();
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

Events_SetBoolTimer() spawns a detached std::thread on every call. If the runtime ends up resetting timers frequently, this can create many concurrent sleeping threads and unnecessary resource usage. Consider using a single background timer thread/dispatch queue, or a cancellable timer primitive, even in scaffold form.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@josesimoes josesimoes left a comment

Choose a reason for hiding this comment

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

Looks good to me.
Want to address any of Copilot review points before merging?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add macOS host nanoCLR target scaffold (targets/osx)

5 participants