Add canon sync
canon sync rewrites the managed block of CMakeLists.txt from .canon.toml, for after either was edited by hand. It prints a unified diff of the change (also with --dry-run), writes only CMakeLists.txt so comments in .canon.toml survive, does nothing if the block already matches, and warns when the build will still fail because .canon.toml declares files that do not exist. doctor's hand-edited-block warning now points to it. - diff: a small line-based unified diff, no external tools - tests: a sync scenario (hand-edited manifest, --dry-run leaves the file untouched, sync from a subdirectory, no-op second run, hand edits in the block replaced); unit tests for plan_sync and unified_diff Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# edited by hand
|
||||
# canon project manifest: what canon declared for this project.
|
||||
# The managed block in CMakeLists.txt is rendered from this file.
|
||||
|
||||
[project]
|
||||
name = "libhello"
|
||||
kind = "lib"
|
||||
stem = "hello"
|
||||
spec = "P1204R0"
|
||||
|
||||
[build]
|
||||
backend = "cmake"
|
||||
cmake-minimum = "3.20"
|
||||
cxx-standard = 23
|
||||
|
||||
[[targets]]
|
||||
name = "libhello"
|
||||
type = "library"
|
||||
sources = ["hello/hello.cpp", "hello/extra.cpp"]
|
||||
|
||||
[[modules]]
|
||||
path = "hello/hello"
|
||||
target = "libhello"
|
||||
unit-test = true
|
||||
|
||||
[[tests]]
|
||||
name = "basics"
|
||||
kind = "driver"
|
||||
path = "tests/basics"
|
||||
target = "libhello"
|
||||
@@ -0,0 +1 @@
|
||||
/build/
|
||||
@@ -0,0 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(libhello VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
option(BUILD_TESTING "Build libhello's tests" ON)
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
# Add your own CMake outside the managed block below.
|
||||
|
||||
# >>> canon:managed >>>
|
||||
# Rendered from .canon.toml; canon rewrites everything between these markers.
|
||||
|
||||
add_library(libhello
|
||||
hello/hello.cpp
|
||||
hello/extra.cpp
|
||||
)
|
||||
set_target_properties(libhello PROPERTIES PREFIX "")
|
||||
target_include_directories(libhello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(libhello PUBLIC cxx_std_23)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(hello.hello.test hello/hello.test.cpp)
|
||||
target_link_libraries(hello.hello.test PRIVATE libhello)
|
||||
add_test(NAME hello/hello.test COMMAND hello.hello.test)
|
||||
|
||||
add_executable(tests.basics tests/basics/driver.cpp)
|
||||
target_link_libraries(tests.basics PRIVATE libhello)
|
||||
add_test(NAME basics COMMAND tests.basics)
|
||||
endif()
|
||||
|
||||
# <<< canon:managed <<<
|
||||
@@ -0,0 +1,12 @@
|
||||
# libhello
|
||||
|
||||
Created by canon. The layout follows [P1204R0](https://wg21.link/p1204r0)
|
||||
(Canonical Project Structure).
|
||||
|
||||
## Build and test
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace hello {
|
||||
int extra() { return 42; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace hello {
|
||||
|
||||
std::string greeting(std::string_view name) {
|
||||
return std::format("Hello, {}!", name);
|
||||
}
|
||||
|
||||
} // namespace hello
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace hello {
|
||||
|
||||
// Returns a greeting for name, e.g. "Hello, World!".
|
||||
[[nodiscard]] std::string greeting(std::string_view name);
|
||||
|
||||
} // namespace hello
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
|
||||
int main() {
|
||||
const auto actual = hello::greeting("World");
|
||||
if (actual != "Hello, World!") {
|
||||
std::println(stderr, "greeting(\"World\") returned \"{}\"", actual);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Functional test: uses libhello only through its public headers, as a consumer would.
|
||||
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
|
||||
int main() {
|
||||
const auto greeting = hello::greeting("libhello");
|
||||
std::println("{}", greeting);
|
||||
return greeting.starts_with("Hello") ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
+26
-1
@@ -4,7 +4,7 @@
|
||||
# canon runs with XDG_CONFIG_HOME set to <work_dir>/<scenario>-config, so user
|
||||
# templates in your own ~/.config never leak into the results.
|
||||
|
||||
set(CANON_SCENARIOS new-lib new-exe add-lib add-exe multi-target exe-tests templates)
|
||||
set(CANON_SCENARIOS new-lib new-exe add-lib add-exe multi-target exe-tests templates sync)
|
||||
|
||||
# Scenarios run canon from other directories, so a relative path would break.
|
||||
get_filename_component(CANON "${CANON}" ABSOLUTE)
|
||||
@@ -120,6 +120,31 @@ function(canon_scenario scenario work_dir out_var)
|
||||
canon_run_fails(${project} add module broken)
|
||||
file(REMOVE ${config_home}/canon/templates/modul.hpp)
|
||||
|
||||
elseif(scenario STREQUAL "sync")
|
||||
canon_run(${dir} new lib libhello)
|
||||
set(project ${dir}/libhello)
|
||||
|
||||
# Declare a source by editing .canon.toml by hand, then bring CMakeLists.txt along.
|
||||
file(WRITE ${project}/hello/extra.cpp "namespace hello {\nint extra() { return 42; }\n}\n")
|
||||
file(READ ${project}/.canon.toml manifest)
|
||||
string(REPLACE "sources = [\"hello/hello.cpp\"]" "sources = [\"hello/hello.cpp\", \"hello/extra.cpp\"]"
|
||||
manifest "${manifest}")
|
||||
file(WRITE ${project}/.canon.toml "# edited by hand\n${manifest}")
|
||||
file(READ ${project}/CMakeLists.txt before)
|
||||
canon_run(${project} sync --dry-run)
|
||||
file(READ ${project}/CMakeLists.txt after)
|
||||
if(NOT before STREQUAL after)
|
||||
message(FATAL_ERROR "canon sync --dry-run changed CMakeLists.txt")
|
||||
endif()
|
||||
canon_run(${project}/hello sync) # from a subdirectory
|
||||
canon_run(${project} sync) # already in sync
|
||||
|
||||
# Hand edits inside the managed block are replaced; the comment in .canon.toml survives.
|
||||
file(READ ${project}/CMakeLists.txt cmake)
|
||||
string(REPLACE "set_target_properties" "add_compile_options(-w)\nset_target_properties" cmake "${cmake}")
|
||||
file(WRITE ${project}/CMakeLists.txt "${cmake}")
|
||||
canon_run(${project} sync)
|
||||
|
||||
else()
|
||||
message(FATAL_ERROR "unknown scenario '${scenario}'")
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user