[M6] Phase 2 self-hosting: cargoxx builds itself from Cargoxx.toml

This commit is contained in:
2026-05-15 13:14:28 +00:00
parent 73aebf183e
commit c46f3aa1f0
7 changed files with 394 additions and 152 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Build outputs # Build outputs
/build/ /build/*
!/build/CMakeLists.txt
/result /result
/result-* /result-*

View File

@@ -1,96 +0,0 @@
cmake_minimum_required(VERSION 3.30)
# Opt into experimental C++ modules dyndep + `import std;` support.
# Required until CMake declares these stable; see CMake docs for the current UUID.
set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_CXX_MODULE_STD ON)
project(cargoxx LANGUAGES CXX VERSION 0.1.0)
# Phase 0: hand-written CMakeLists.txt. Replaced by generated build/CMakeLists.txt
# at milestone M3 once cargoxx can build itself. See TECH_SPEC.md §15.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
endif()
# TECH_SPEC.md §17: -Wall -Wextra -Wpedantic -Wconversion, -Werror in CI.
add_compile_options(-Wall -Wextra -Wpedantic -Wconversion)
option(CARGOXX_WERROR "Treat warnings as errors" OFF)
if(CARGOXX_WERROR)
add_compile_options(-Werror)
endif()
find_package(SQLite3 REQUIRED)
find_package(reproc REQUIRED)
# ----- cargoxx library: module units + implementation units -----
add_library(cargoxx STATIC)
target_include_directories(cargoxx SYSTEM PRIVATE third_party)
target_sources(cargoxx
PRIVATE
src/util/error.cpp
src/util/semver.cpp
src/manifest/parser.cpp
src/manifest/writer.cpp
src/layout/layout.cpp
src/lockfile/lockfile.cpp
src/linkdb/database.cpp
src/linkdb/overlay.cpp
src/codegen/flake.cpp
src/codegen/cmake.cpp
src/exec/subprocess.cpp
src/resolver/nixpkgs_probe.cpp
src/resolver/nix_cmake_scan.cpp
src/resolver/pc_scan.cpp
src/resolver/conan_probe.cpp
src/resolver/vcpkg_probe.cpp
src/resolver/verify_link.cpp
src/resolver/discover.cpp
src/resolver/search_devbox.cpp
src/resolver/nixpkgs_git.cpp
src/resolver/version_resolve.cpp
src/cli/cmd_new.cpp
src/cli/cmd_build.cpp
src/cli/cmd_run.cpp
src/cli/cmd_test.cpp
src/cli/cmd_clean.cpp
src/cli/cmd_add.cpp
src/cli/cmd_remove.cpp
src/cli/cmd_linkdb_add.cpp
src/cli/run.cpp
PUBLIC
FILE_SET CXX_MODULES FILES
src/lib.cppm
src/util/util.cppm
src/exec/exec.cppm
src/manifest/manifest.cppm
src/lockfile/lockfile.cppm
src/layout/layout.cppm
src/linkdb/linkdb.cppm
src/resolver/resolver.cppm
src/codegen/codegen.cppm
src/cli/cli.cppm
)
target_link_libraries(cargoxx PRIVATE SQLite::SQLite3 reproc)
# ----- cargoxx binary -----
add_executable(cargoxx_bin src/main.cpp)
set_target_properties(cargoxx_bin PROPERTIES OUTPUT_NAME cargoxx)
target_link_libraries(cargoxx_bin PRIVATE cargoxx)
# ----- tests -----
option(CARGOXX_BUILD_TESTS "Build cargoxx tests" ON)
if(CARGOXX_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

24
Cargoxx.lock Normal file
View File

@@ -0,0 +1,24 @@
version = 1
[[package]]
dependencies = [ 'reproc *', 'sqlite *', 'catch2_3 *' ]
name = 'cargoxx'
version = '0.1.0'
[[package]]
linkdb_source = 'nix-probe'
name = 'reproc'
nixpkgs_attr = 'reproc'
version = '*'
[[package]]
linkdb_source = 'pkg-config'
name = 'sqlite'
nixpkgs_attr = 'sqlite'
version = '*'
[[package]]
linkdb_source = 'nix-probe'
name = 'catch2_3'
nixpkgs_attr = 'catch2_3'
version = '*'

View File

@@ -619,18 +619,17 @@ User-facing errors are formatted via `util::format(Error)` and printed to stderr
## 15. Bootstrap and self-hosting ## 15. Bootstrap and self-hosting
Three phases. **Phase 0 (historical) — hand-written `CMakeLists.txt` and `flake.nix` at the repo root.**
**Phase 0 — hand-written CMake (commits before milestone M3).** **Phase 2 (current, since M6) — fully self-hosted.**
`CMakeLists.txt` and `flake.nix` at the repo root are written by humans. `cargoxx` builds `cargoxx`. `Cargoxx.toml` describes cargoxx's own deps with nixpkgs names: `sqlite`, `reproc`, `catch2_3`. `cargoxx build` runs the auto-resolver chain (nixpkgs probe → realize → nix_cmake_scan → pc_scan), confirms each recipe via verify_link, and generates `build/CMakeLists.txt` and the root `flake.nix`. Both files are committed (tracked) so the build is reproducible without first building cargoxx, and `[build].include_dirs = ["third_party"]` keeps the vendored headers on the include path.
**Phase 1 — generated CMake, hand-written flake.** Bootstrap path:
At milestone M3 (codegen complete), commit a populated `Cargoxx.toml`. Generate `build/CMakeLists.txt` from it. Delete the root `CMakeLists.txt`. The root `flake.nix` stays hand-written because cargoxx doesn't know about its own host-language deps yet. ```
pre-built cargoxx → cargoxx build → next cargoxx
```
**Phase 2 — fully self-hosted.** A clean clone with an empty `~/.cache/cargoxx/linkdb.sqlite` auto-resolves all three deps on first `cargoxx build` (sqlite goes through pkg-config because nixpkgs ships no SQLite3Config.cmake; reproc/catch2_3 go through nix_cmake_scan). For continuity, a known-good cargoxx binary is shipped as a release artifact; from-scratch bootstrap is not in v0.1 scope.
At milestone M5, vendor all third-party headers into `third_party/` and have cargoxx generate the flake too. The bootstrap path becomes: pre-built cargoxx binary → run `cargoxx build` → produce next cargoxx.
For continuity, ship a known-good cargoxx binary as a release artifact. Anyone bootstrapping from source clones the repo, downloads the latest release binary, and runs `./bootstrap-cargoxx build`. If we ever want to bootstrap from absolute zero, `scripts/bootstrap-build.sh` does it with a hand-written CMake invocation.
--- ---

351
build/CMakeLists.txt Normal file
View File

@@ -0,0 +1,351 @@
cmake_minimum_required(VERSION 3.30)
set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_CXX_MODULE_STD ON)
project(cargoxx LANGUAGES CXX)
# Generated by cargoxx — do not edit.
# Source of truth: ../Cargoxx.toml
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options(-Wall -Wextra -Wpedantic -Wconversion)
# ----- dependencies -----
find_package(reproc CONFIG REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3)
find_package(Catch2 CONFIG REQUIRED)
# ----- library target -----
add_library(cargoxx STATIC)
target_sources(cargoxx
PUBLIC
FILE_SET CXX_MODULES BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/.. FILES
../src/cli/cli.cppm
../src/codegen/codegen.cppm
../src/exec/exec.cppm
../src/layout/layout.cppm
../src/lib.cppm
../src/linkdb/linkdb.cppm
../src/lockfile/lockfile.cppm
../src/manifest/manifest.cppm
../src/resolver/resolver.cppm
../src/util/util.cppm
PRIVATE
../src/cli/cmd_add.cpp
../src/cli/cmd_build.cpp
../src/cli/cmd_clean.cpp
../src/cli/cmd_linkdb_add.cpp
../src/cli/cmd_new.cpp
../src/cli/cmd_remove.cpp
../src/cli/cmd_run.cpp
../src/cli/cmd_test.cpp
../src/cli/run.cpp
../src/codegen/cmake.cpp
../src/codegen/flake.cpp
../src/exec/subprocess.cpp
../src/layout/layout.cpp
../src/linkdb/database.cpp
../src/linkdb/overlay.cpp
../src/lockfile/lockfile.cpp
../src/manifest/parser.cpp
../src/manifest/writer.cpp
../src/resolver/conan_probe.cpp
../src/resolver/discover.cpp
../src/resolver/nix_cmake_scan.cpp
../src/resolver/nixpkgs_git.cpp
../src/resolver/nixpkgs_probe.cpp
../src/resolver/pc_scan.cpp
../src/resolver/search_devbox.cpp
../src/resolver/vcpkg_probe.cpp
../src/resolver/verify_link.cpp
../src/resolver/version_resolve.cpp
../src/util/error.cpp
../src/util/semver.cpp
)
target_include_directories(cargoxx SYSTEM PRIVATE ../third_party)
target_link_libraries(cargoxx PUBLIC
reproc
PkgConfig::SQLITE3
)
# ----- binary target -----
add_executable(cargoxx_bin ../src/main.cpp)
set_target_properties(cargoxx_bin PROPERTIES OUTPUT_NAME cargoxx)
target_link_libraries(cargoxx_bin PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
)
# ----- tests -----
enable_testing()
include(Catch)
add_executable(test_cmd_add ../tests/cmd_add.cpp)
target_link_libraries(test_cmd_add PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_add)
add_executable(test_cmd_build ../tests/cmd_build.cpp)
target_link_libraries(test_cmd_build PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_build)
add_executable(test_cmd_clean ../tests/cmd_clean.cpp)
target_link_libraries(test_cmd_clean PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_clean)
add_executable(test_cmd_new ../tests/cmd_new.cpp)
target_link_libraries(test_cmd_new PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_new)
add_executable(test_cmd_remove ../tests/cmd_remove.cpp)
target_link_libraries(test_cmd_remove PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_remove)
add_executable(test_cmd_run ../tests/cmd_run.cpp)
target_link_libraries(test_cmd_run PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_cmd_run)
add_executable(test_codegen_cmake ../tests/codegen_cmake.cpp)
target_link_libraries(test_codegen_cmake PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_codegen_cmake)
add_executable(test_codegen_flake ../tests/codegen_flake.cpp)
target_link_libraries(test_codegen_flake PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_codegen_flake)
add_executable(test_conan_probe_live ../tests/conan_probe_live.cpp)
target_link_libraries(test_conan_probe_live PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_conan_probe_live)
add_executable(test_conan_probe_parse ../tests/conan_probe_parse.cpp)
target_link_libraries(test_conan_probe_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_conan_probe_parse)
add_executable(test_devbox_resolve_live ../tests/devbox_resolve_live.cpp)
target_link_libraries(test_devbox_resolve_live PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_devbox_resolve_live)
add_executable(test_devbox_resolve_parse ../tests/devbox_resolve_parse.cpp)
target_link_libraries(test_devbox_resolve_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_devbox_resolve_parse)
add_executable(test_exec_run ../tests/exec_run.cpp)
target_link_libraries(test_exec_run PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_exec_run)
add_executable(test_layout_discovery ../tests/layout_discovery.cpp)
target_link_libraries(test_layout_discovery PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_layout_discovery)
add_executable(test_linkdb_lookup ../tests/linkdb_lookup.cpp)
target_link_libraries(test_linkdb_lookup PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_linkdb_lookup)
add_executable(test_linkdb_overlay ../tests/linkdb_overlay.cpp)
target_link_libraries(test_linkdb_overlay PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_linkdb_overlay)
add_executable(test_lockfile_round_trip ../tests/lockfile_round_trip.cpp)
target_link_libraries(test_lockfile_round_trip PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_lockfile_round_trip)
add_executable(test_manifest_parse ../tests/manifest_parse.cpp)
target_link_libraries(test_manifest_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_manifest_parse)
add_executable(test_manifest_write ../tests/manifest_write.cpp)
target_link_libraries(test_manifest_write PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_manifest_write)
add_executable(test_nix_cmake_scan_live ../tests/nix_cmake_scan_live.cpp)
target_link_libraries(test_nix_cmake_scan_live PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_nix_cmake_scan_live)
add_executable(test_nix_cmake_scan_parse ../tests/nix_cmake_scan_parse.cpp)
target_link_libraries(test_nix_cmake_scan_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_nix_cmake_scan_parse)
add_executable(test_nixpkgs_git_resolve ../tests/nixpkgs_git_resolve.cpp)
target_link_libraries(test_nixpkgs_git_resolve PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_nixpkgs_git_resolve)
add_executable(test_nixpkgs_probe_live ../tests/nixpkgs_probe_live.cpp)
target_link_libraries(test_nixpkgs_probe_live PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_nixpkgs_probe_live)
add_executable(test_nixpkgs_probe_parse ../tests/nixpkgs_probe_parse.cpp)
target_link_libraries(test_nixpkgs_probe_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_nixpkgs_probe_parse)
add_executable(test_semver_satisfies ../tests/semver_satisfies.cpp)
target_link_libraries(test_semver_satisfies PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_semver_satisfies)
add_executable(test_util_error ../tests/util_error.cpp)
target_link_libraries(test_util_error PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_util_error)
add_executable(test_vcpkg_probe_live ../tests/vcpkg_probe_live.cpp)
target_link_libraries(test_vcpkg_probe_live PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_vcpkg_probe_live)
add_executable(test_vcpkg_probe_parse ../tests/vcpkg_probe_parse.cpp)
target_link_libraries(test_vcpkg_probe_parse PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_vcpkg_probe_parse)
add_executable(test_verify_link_unit ../tests/verify_link_unit.cpp)
target_link_libraries(test_verify_link_unit PRIVATE
cargoxx
reproc
PkgConfig::SQLITE3
Catch2::Catch2
Catch2::Catch2WithMain
)
catch_discover_tests(test_verify_link_unit)

View File

@@ -1,5 +1,5 @@
{ {
description = "cargoxx Cargo-style frontend for modern C++"; description = "cargoxx";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -11,21 +11,22 @@
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
in { in {
devShells.default = pkgs.gcc15Stdenv.mkDerivation { devShell = pkgs.gcc15Stdenv.mkDerivation {
name = "cargoxx-dev"; name = "shell";
version = "0.1.0"; version = "1.0";
nativeBuildInputs = [ nativeBuildInputs = [
pkgs.cmake
pkgs.ninja pkgs.ninja
pkgs.git pkgs.cmake
pkgs.pkg-config pkgs.pkg-config
]; ];
buildInputs = [ buildInputs = [
pkgs.sqlite
pkgs.reproc pkgs.reproc
pkgs.sqlite
pkgs.catch2_3 pkgs.catch2_3
]; ];
hardeningDisable = [ "all" ]; hardeningDisable = [
"all"
];
}; };
}); });
} }

View File

@@ -1,38 +0,0 @@
find_package(Catch2 3 REQUIRED CONFIG)
include(Catch)
function(cargoxx_add_test name)
add_executable(${name} ${name}.cpp)
target_link_libraries(${name} PRIVATE cargoxx Catch2::Catch2WithMain)
catch_discover_tests(${name})
endfunction()
cargoxx_add_test(util_error)
cargoxx_add_test(semver_satisfies)
cargoxx_add_test(exec_run)
cargoxx_add_test(manifest_parse)
cargoxx_add_test(manifest_write)
cargoxx_add_test(layout_discovery)
cargoxx_add_test(lockfile_round_trip)
cargoxx_add_test(linkdb_lookup)
cargoxx_add_test(linkdb_overlay)
cargoxx_add_test(codegen_flake)
cargoxx_add_test(codegen_cmake)
cargoxx_add_test(cmd_new)
cargoxx_add_test(cmd_build)
cargoxx_add_test(cmd_run)
cargoxx_add_test(cmd_clean)
cargoxx_add_test(cmd_add)
cargoxx_add_test(cmd_remove)
cargoxx_add_test(nixpkgs_probe_parse)
cargoxx_add_test(nixpkgs_probe_live)
cargoxx_add_test(nix_cmake_scan_parse)
cargoxx_add_test(nix_cmake_scan_live)
cargoxx_add_test(conan_probe_parse)
cargoxx_add_test(conan_probe_live)
cargoxx_add_test(vcpkg_probe_parse)
cargoxx_add_test(vcpkg_probe_live)
cargoxx_add_test(verify_link_unit)
cargoxx_add_test(devbox_resolve_parse)
cargoxx_add_test(devbox_resolve_live)
cargoxx_add_test(nixpkgs_git_resolve)