Initial commit
Add a minimal no-core Rust executable built by invoking rustc directly from CMake. The project avoids Cargo and Rust core, alloc, and std, writes to stdout through platform APIs, and supports optimized Ninja release builds. Co-Authored-By: luna (openrouter/openai/gpt-5.6-luna): project setup and build
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.cache
|
||||
/target
|
||||
.env
|
||||
/build
|
||||
@@ -0,0 +1,119 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a minimal Rust experiment that builds one executable without Cargo
|
||||
and without linking Rust `core`, `alloc`, or `std`. CMake invokes `rustc`
|
||||
directly. The executable writes `Hello` to stdout and exits.
|
||||
|
||||
The project currently contains:
|
||||
|
||||
- `main.rs`: the `#![no_core]` Rust program
|
||||
- `CMakeLists.txt`: the direct `rustc` custom command
|
||||
- `.gitignore`: ignores generated build output
|
||||
|
||||
## Non-Negotiable No-Core Design
|
||||
|
||||
- Keep `#![no_core]` in `main.rs`.
|
||||
- Do not replace `#![no_core]` with `#![no_std]`; `no_std` still links
|
||||
Rust `core`.
|
||||
- Do not add Cargo files or invoke Cargo.
|
||||
- Do not import or link `core`, `alloc`, or `std`.
|
||||
- Keep the required language-item and marker-trait definitions local to
|
||||
`main.rs` unless the user explicitly asks for a different design.
|
||||
- Use primitive Rust types and explicit platform FFI instead of library
|
||||
helpers.
|
||||
- Keep unsafe code inside small safe wrappers. The public call site for
|
||||
stdout should remain a normal string call such as:
|
||||
|
||||
```rust
|
||||
write_stdout("Hello\\n");
|
||||
```
|
||||
|
||||
## Output Implementation
|
||||
|
||||
- Unix builds call the libc `write` function with file descriptor `1`.
|
||||
- Windows builds call `GetStdHandle`, `WriteFile`, and `ExitProcess`
|
||||
directly through `kernel32`.
|
||||
- The Windows build must not link the C runtime. This keeps the executable
|
||||
small and uses `/NODEFAULTLIB` with the `kernel32` import library.
|
||||
- Preserve the safe `write_stdout(&str)` interface when changing output.
|
||||
|
||||
## Build System
|
||||
|
||||
CMake is configured with `LANGUAGES NONE` and calls `rustc` through an
|
||||
`add_custom_command`. `find_program(RUSTC rustc REQUIRED)` locates the
|
||||
compiler.
|
||||
|
||||
Configure and build a release executable with Ninja:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Run it on Unix:
|
||||
|
||||
```sh
|
||||
./build/no_core_write
|
||||
```
|
||||
|
||||
Run it on Windows:
|
||||
|
||||
```powershell
|
||||
.\build\no_core_write.exe
|
||||
```
|
||||
|
||||
`CMAKE_BUILD_TYPE` is translated explicitly to rustc flags:
|
||||
|
||||
- `Release`: `-C opt-level=3 -C debuginfo=0`
|
||||
- `RelWithDebInfo`: `-C opt-level=3 -C debuginfo=2`
|
||||
- Other or unset configurations: `-C opt-level=0 -C debuginfo=2`
|
||||
|
||||
`no_core` is unstable, so CMake passes `RUSTC_BOOTSTRAP=1` to the direct
|
||||
rustc invocation. A nightly compiler may be used instead, but Cargo is
|
||||
still not permitted.
|
||||
|
||||
## Verification
|
||||
|
||||
After changing the source or build configuration, run a clean release build
|
||||
and execute the program:
|
||||
|
||||
```sh
|
||||
rm -rf build
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
./build/no_core_write
|
||||
```
|
||||
|
||||
On Windows, use `Remove-Item -Recurse -Force build` and run
|
||||
`build\\no_core_write.exe` instead. Confirm that stdout contains `Hello`.
|
||||
|
||||
When size matters, inspect the Windows executable size after a release
|
||||
build. Avoid adding runtime libraries, formatting code, or Rust library
|
||||
dependencies.
|
||||
|
||||
## Commit Messages
|
||||
|
||||
- Follow the 50/72 rule: subject lines are at most 50 characters and body
|
||||
lines are wrapped at 72 characters.
|
||||
- Use conventional prefixes such as `feat:`, `fix:`, `docs:`, `chore:`,
|
||||
and `ci:`.
|
||||
- Separate the subject from the body with a blank line.
|
||||
- Keep commit messages concise.
|
||||
- Include a `Co-Authored-By:` trailer for every agent or model that
|
||||
contributed to the commit, one trailer per co-author:
|
||||
|
||||
```text
|
||||
Co-Authored-By: qwen (lmstudio/qwen3.6-27b-mtp): wrote tests + build
|
||||
Co-Authored-By: luna (openai/gpt-5.6-luna): reviewed edge cases
|
||||
```
|
||||
|
||||
## Editing Guidelines
|
||||
|
||||
- Keep changes minimal and directly related to the request.
|
||||
- Use four-space indentation in Rust and CMake.
|
||||
- Keep normal text in Markdown within 80 columns where practical.
|
||||
- Do not add speculative abstractions or dependencies.
|
||||
- Update this file when the build workflow, no-core constraints, supported
|
||||
platforms, or source layout changes.
|
||||
@@ -0,0 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(no_core_write LANGUAGES NONE)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_program(RUSTC rustc REQUIRED)
|
||||
|
||||
set(RUST_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/main.rs")
|
||||
set(RUST_BINARY "${CMAKE_CURRENT_BINARY_DIR}/no_core_write${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
|
||||
# no_core is an unstable rustc feature. RUSTC_BOOTSTRAP lets this deliberately
|
||||
# minimal example be built with the installed compiler without Cargo or core.
|
||||
set(RUSTC_ENV "RUSTC_BOOTSTRAP=1")
|
||||
set(RUSTC_FLAGS -C panic=abort)
|
||||
|
||||
# CMAKE_BUILD_TYPE does not automatically affect a custom rustc command.
|
||||
# Pass the corresponding optimization/debug-info settings explicitly.
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
list(APPEND RUSTC_FLAGS -C opt-level=3 -C debuginfo=0)
|
||||
elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
list(APPEND RUSTC_FLAGS -C opt-level=3 -C debuginfo=2)
|
||||
else()
|
||||
list(APPEND RUSTC_FLAGS -C opt-level=0 -C debuginfo=2)
|
||||
endif()
|
||||
|
||||
# A no_core binary does not get Rust's normal startup/linker setup. Link the
|
||||
# small platform system/runtime library needed by the stdout implementation.
|
||||
if (WIN32)
|
||||
# Use Win32 directly instead of the C runtime; this keeps the executable
|
||||
# small while still writing to the console's stdout handle.
|
||||
set(RUSTC_LINK_ARGS
|
||||
-l kernel32
|
||||
-C
|
||||
"link-args=/NODEFAULTLIB /ENTRY:mainCRTStartup /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF"
|
||||
)
|
||||
elseif (UNIX)
|
||||
set(RUSTC_LINK_ARGS -C "link-args=-lc")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${RUST_BINARY}"
|
||||
COMMAND "${CMAKE_COMMAND}" -E env "${RUSTC_ENV}" "${RUSTC}"
|
||||
"${RUST_SOURCE}"
|
||||
--crate-name no_core_write
|
||||
--crate-type bin
|
||||
-o "${RUST_BINARY}"
|
||||
${RUSTC_FLAGS}
|
||||
${RUSTC_LINK_ARGS}
|
||||
DEPENDS "${RUST_SOURCE}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(no_core_write ALL DEPENDS "${RUST_BINARY}")
|
||||
@@ -0,0 +1,95 @@
|
||||
#![no_core]
|
||||
#![no_main]
|
||||
#![feature(no_core, lang_items, auto_traits)]
|
||||
#![allow(internal_features)]
|
||||
|
||||
// These three marker traits are the minimum language items needed when core is
|
||||
// not linked. They are compiler contracts, not a replacement for core.
|
||||
#[lang = "pointee_sized"]
|
||||
trait PointeeSized {}
|
||||
|
||||
#[lang = "meta_sized"]
|
||||
trait MetaSized: PointeeSized {}
|
||||
|
||||
#[lang = "sized"]
|
||||
trait Sized: MetaSized {}
|
||||
|
||||
#[lang = "copy"]
|
||||
trait Copy {}
|
||||
|
||||
#[lang = "freeze"]
|
||||
unsafe auto trait Freeze {}
|
||||
|
||||
#[lang = "eh_personality"]
|
||||
extern "C" fn eh_personality() {}
|
||||
|
||||
#[lang = "panic_info"]
|
||||
struct PanicInfo;
|
||||
|
||||
#[lang = "panic_impl"]
|
||||
fn panic_impl(_: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[lang = "panic_cannot_unwind"]
|
||||
fn panic_cannot_unwind() -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
unsafe extern "C" {
|
||||
fn write(fd: i32, buffer: *const u8, count: usize) -> isize;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe extern "system" {
|
||||
fn GetStdHandle(which: u32) -> *mut u8;
|
||||
fn WriteFile(
|
||||
handle: *mut u8,
|
||||
buffer: *const u8,
|
||||
count: u32,
|
||||
written: *mut u32,
|
||||
overlapped: *mut u8,
|
||||
) -> i32;
|
||||
fn ExitProcess(status: u32) -> !;
|
||||
}
|
||||
|
||||
// A string slice is a (pointer, length) pair. This keeps the public helper
|
||||
// ergonomic without importing core just to call str::as_ptr and str::len.
|
||||
#[repr(C)]
|
||||
union StringParts<'a> {
|
||||
string: &'a str,
|
||||
bytes: (*const u8, usize),
|
||||
}
|
||||
|
||||
fn write_stdout(message: &str) {
|
||||
let (buffer, length) = unsafe { StringParts { string: message }.bytes };
|
||||
|
||||
unsafe {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
write(1, buffer, length);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let handle = GetStdHandle(0xffff_fff5); // STD_OUTPUT_HANDLE
|
||||
let mut written: u32 = 0;
|
||||
WriteFile(handle, buffer, length as u32, &mut written, 0 as *mut u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn main() -> i32 {
|
||||
write_stdout("Hello\n");
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn mainCRTStartup() -> ! {
|
||||
write_stdout("Hello\n");
|
||||
unsafe { ExitProcess(0) }
|
||||
}
|
||||
Reference in New Issue
Block a user