diff --git a/docs/reference.md b/docs/reference.md index 9e9fb8a..4ee9174 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -259,11 +259,10 @@ putup # Pass 2: Build with generated configs **How it works:** 1. Installs root config if `--config` is specified -2. Copies subdir `tup.config` files from config root to build tree (out-of-tree only) -3. Parses all Tupfiles using root `tup.config` only -4. Identifies rules where any output ends with `tup.config` -5. Executes only those rules (plus their dependencies) -6. Does not write to `.pup/index` (avoids conflict with subsequent build) +2. Parses all Tupfiles using root `tup.config` only +3. Identifies rules where any output ends with `tup.config` +4. Executes only those rules (plus their dependencies) +5. Does not write to `.pup/index` (avoids conflict with subsequent build) **Relevant Options:** - `-v` - Verbose output @@ -278,7 +277,7 @@ putup # Pass 2: Build with generated configs **Using --config for pre-made configs:** -The `--config` option copies an existing config file to the output directory as the root `tup.config`. It then continues with steps 2–6 above: copying subdir configs and running any config-generating rules. Useful for: +The `--config` option copies an existing config file to the output directory as the root `tup.config`. It then continues with steps 2–5 above, running any config-generating rules. Useful for: - Cross-compilation with pre-made toolchain configs - CI/CD where configs are externally managed - Mixed workflows with a static root config + auto-generated subdir configs @@ -288,26 +287,25 @@ putup configure -B build --config configs/arm-cross.config putup configure -B build-debug -c debug.config ``` -**Subdir tup.config copying (step 2):** +**Subdir config installation:** -For out-of-tree builds (`config_root != output_root`), configure automatically copies any `tup.config` files found in subdirectories of the config root to the corresponding locations in the build tree. The root-level `tup.config` is excluded (handled by `--config` or config-generating rules). +Subdirectory configs are installed via Tupfile copy rules — putup has no built-in config copying. Each subdirectory that needs a scoped `tup.config` ships a `defaults.config` alongside its Tupfile with a copy rule: -This enables per-component scoped configs: each subdirectory ships a `tup.config` alongside its Tupfile. At configure time, these are installed into the build tree where scoped config merging (§6.1) picks them up during the build. +```tup +: defaults.config |> cp %f %o |> tup.config +``` + +At configure time, these rules are discovered and executed like any other config-generating rule. The resulting `tup.config` files are placed in the build tree where scoped config merging (§6.1) picks them up during the build. ```bash -# Config root has per-component configs: -# gmp/tup.config, mpfr/tup.config, mpc/tup.config -# -# configure installs root config AND copies subdir configs: +# Each subdir Tupfile has a copy rule for its defaults.config: putup configure --config configs/toolchain.config -C . -S ../src -B ../build # → ../build/tup.config (from --config) -# → ../build/gmp/tup.config (copied from gmp/tup.config) -# → ../build/mpfr/tup.config (copied from mpfr/tup.config) -# → ../build/mpc/tup.config (copied from mpc/tup.config) +# → ../build/gmp/tup.config (from gmp/Tupfile copy rule) +# → ../build/mpfr/tup.config (from mpfr/Tupfile copy rule) +# → ../build/mpc/tup.config (from mpc/Tupfile copy rule) ``` -For in-tree builds (`config_root == output_root`), step 2 is a no-op — the configs are already in place. - **Important:** You must run `putup configure` before `putup build`. If you skip the configure step, `putup build` will error: ``` @@ -1648,7 +1646,7 @@ S = ../.. GMP_DIR = gmp → $(S)/$(GMP_DIR) = ../../gmp ✓ S = .. GMP_DIR = ../gmp → $(S)/$(GMP_DIR) = ../../gmp ✓ ``` -**Scoped `tup.config` defaults:** Components can also ship default config values in their own `tup.config` — parent configs override child configs on collision (see §6.1 *Scoped Config Merging*). +**Scoped `tup.config` defaults:** Components ship default config values in a `defaults.config` file with a Tupfile copy rule (`cp %f %o`) that installs it as `tup.config` during configure. Parent configs override child configs on collision (see §6.1 *Scoped Config Merging*). See `examples/bsp/gcc/` for a complete working example with three interdependent libraries. diff --git a/examples/bsp/Makefile.pup b/examples/bsp/Makefile.pup index d78710c..68b76a8 100644 --- a/examples/bsp/Makefile.pup +++ b/examples/bsp/Makefile.pup @@ -40,38 +40,38 @@ configure: resolve-mpn setup-host-configs # Copy host-specific library configs when building for non-Linux hosts. # Linux configs are the in-tree defaults; other hosts overlay from configs/host-$(HOST)/. -# To restore Linux defaults: git checkout -- gcc/*/tup.config binutils/tup.config +# To restore Linux defaults: git checkout -- gcc/*/defaults.config binutils/defaults.config setup-host-configs: @if [ "$(HOST)" != "linux" ] && [ -d configs/host-$(HOST) ]; then \ for cfg in configs/host-$(HOST)/*.config; do \ lib=$$(basename "$$cfg" .config); \ - if [ -f "$$lib/tup.config" ]; then \ - echo " COPY $$lib/tup.config (host-$(HOST))"; \ - cp "$$cfg" "$$lib/tup.config"; \ - elif [ -f "gcc/$$lib/tup.config" ]; then \ - echo " COPY gcc/$$lib/tup.config (host-$(HOST))"; \ - cp "$$cfg" "gcc/$$lib/tup.config"; \ + if [ -f "$$lib/defaults.config" ]; then \ + echo " COPY $$lib/defaults.config (host-$(HOST))"; \ + cp "$$cfg" "$$lib/defaults.config"; \ + elif [ -f "gcc/$$lib/defaults.config" ]; then \ + echo " COPY gcc/$$lib/defaults.config (host-$(HOST))"; \ + cp "$$cfg" "gcc/$$lib/defaults.config"; \ fi; \ done; \ fi # Resolve mpn sources for the selected CPU target. -# - gcc/gmp/mpn/tup.config: source lists and per-function toggles (child scope) -# - gcc/gmp/tup.config: GMP_MPARAM and ASM_ENABLED (parent scope, visible to gmp/Tupfile) +# - gcc/gmp/mpn/defaults.config: source lists and per-function toggles (child scope) +# - gcc/gmp/defaults.config: GMP_MPARAM and ASM_ENABLED (parent scope, visible to gmp/Tupfile) resolve-mpn: - @scripts/resolve-mpn.sh $(MPN_CPU) $(SRCDIR)/gcc/gmp/mpn > gcc/gmp/mpn/tup.config - @grep -v '^CONFIG_GMP_MPARAM=\|^CONFIG_ASM_ENABLED=\|^CONFIG_NO_ASM=' gcc/gmp/tup.config > gcc/gmp/tup.config.tmp && mv gcc/gmp/tup.config.tmp gcc/gmp/tup.config + @scripts/resolve-mpn.sh $(MPN_CPU) $(SRCDIR)/gcc/gmp/mpn > gcc/gmp/mpn/defaults.config + @grep -v '^CONFIG_GMP_MPARAM=\|^CONFIG_ASM_ENABLED=\|^CONFIG_NO_ASM=' gcc/gmp/defaults.config > gcc/gmp/defaults.config.tmp && mv gcc/gmp/defaults.config.tmp gcc/gmp/defaults.config @if [ "$(MPN_CPU)" != "generic" ]; then \ arch=$${MPN_CPU%%/*}; \ if [ -f "$(SRCDIR)/gcc/gmp/mpn/$$arch/gmp-mparam.h" ]; then \ - echo "CONFIG_GMP_MPARAM=mpn/$$arch/gmp-mparam.h" >> gcc/gmp/tup.config; \ + echo "CONFIG_GMP_MPARAM=mpn/$$arch/gmp-mparam.h" >> gcc/gmp/defaults.config; \ else \ - echo "CONFIG_GMP_MPARAM=mpn/generic/gmp-mparam.h" >> gcc/gmp/tup.config; \ + echo "CONFIG_GMP_MPARAM=mpn/generic/gmp-mparam.h" >> gcc/gmp/defaults.config; \ fi; \ - echo "CONFIG_ASM_ENABLED=y" >> gcc/gmp/tup.config; \ + echo "CONFIG_ASM_ENABLED=y" >> gcc/gmp/defaults.config; \ else \ - echo "CONFIG_GMP_MPARAM=mpn/generic/gmp-mparam.h" >> gcc/gmp/tup.config; \ - echo "CONFIG_NO_ASM=1" >> gcc/gmp/tup.config; \ + echo "CONFIG_GMP_MPARAM=mpn/generic/gmp-mparam.h" >> gcc/gmp/defaults.config; \ + echo "CONFIG_NO_ASM=1" >> gcc/gmp/defaults.config; \ fi # Multi-variant: build for multiple platforms in parallel diff --git a/examples/bsp/binutils/Tupfile b/examples/bsp/binutils/Tupfile index fba2135..a1ec142 100644 --- a/examples/bsp/binutils/Tupfile +++ b/examples/bsp/binutils/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # binutils — cross-assembler (as) + cross-archiver (ar) diff --git a/examples/bsp/binutils/tup.config b/examples/bsp/binutils/defaults.config similarity index 100% rename from examples/bsp/binutils/tup.config rename to examples/bsp/binutils/defaults.config diff --git a/examples/bsp/gcc/gcc/Tupfile b/examples/bsp/gcc/gcc/Tupfile index da67912..d4bdcc6 100644 --- a/examples/bsp/gcc/gcc/Tupfile +++ b/examples/bsp/gcc/gcc/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # GCC 15.2.0 — C compiler backend diff --git a/examples/bsp/gcc/gcc/tup.config b/examples/bsp/gcc/gcc/defaults.config similarity index 100% rename from examples/bsp/gcc/gcc/tup.config rename to examples/bsp/gcc/gcc/defaults.config diff --git a/examples/bsp/gcc/gmp/Tupfile b/examples/bsp/gcc/gmp/Tupfile index 110756b..8debbce 100644 --- a/examples/bsp/gcc/gmp/Tupfile +++ b/examples/bsp/gcc/gmp/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # GMP 6.2.1 diff --git a/examples/bsp/gcc/gmp/tup.config b/examples/bsp/gcc/gmp/defaults.config similarity index 100% rename from examples/bsp/gcc/gmp/tup.config rename to examples/bsp/gcc/gmp/defaults.config diff --git a/examples/bsp/gcc/gmp/mpn/Tupfile b/examples/bsp/gcc/gmp/mpn/Tupfile index a5f0d83..9ab015d 100644 --- a/examples/bsp/gcc/gmp/mpn/Tupfile +++ b/examples/bsp/gcc/gmp/mpn/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # MPN — low-level multiprecision arithmetic # diff --git a/examples/bsp/gcc/gmp/mpn/tup.config b/examples/bsp/gcc/gmp/mpn/defaults.config similarity index 100% rename from examples/bsp/gcc/gmp/mpn/tup.config rename to examples/bsp/gcc/gmp/mpn/defaults.config diff --git a/examples/bsp/gcc/libbacktrace/Tupfile b/examples/bsp/gcc/libbacktrace/Tupfile index 2a4e143..7efcb66 100644 --- a/examples/bsp/gcc/libbacktrace/Tupfile +++ b/examples/bsp/gcc/libbacktrace/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # libbacktrace - stack trace support (GCC 15.2.0) diff --git a/examples/bsp/gcc/libbacktrace/tup.config b/examples/bsp/gcc/libbacktrace/defaults.config similarity index 100% rename from examples/bsp/gcc/libbacktrace/tup.config rename to examples/bsp/gcc/libbacktrace/defaults.config diff --git a/examples/bsp/gcc/libcody/Tupfile b/examples/bsp/gcc/libcody/Tupfile index 68488f0..9bd128c 100644 --- a/examples/bsp/gcc/libcody/Tupfile +++ b/examples/bsp/gcc/libcody/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # libcody - C++ modules protocol library diff --git a/examples/bsp/gcc/libcody/tup.config b/examples/bsp/gcc/libcody/defaults.config similarity index 100% rename from examples/bsp/gcc/libcody/tup.config rename to examples/bsp/gcc/libcody/defaults.config diff --git a/examples/bsp/gcc/libcpp/Tupfile b/examples/bsp/gcc/libcpp/Tupfile index 6d995c6..85e70c9 100644 --- a/examples/bsp/gcc/libcpp/Tupfile +++ b/examples/bsp/gcc/libcpp/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # libcpp - C preprocessor library (GCC 15.2.0) diff --git a/examples/bsp/gcc/libcpp/tup.config b/examples/bsp/gcc/libcpp/defaults.config similarity index 100% rename from examples/bsp/gcc/libcpp/tup.config rename to examples/bsp/gcc/libcpp/defaults.config diff --git a/examples/bsp/gcc/libdecnumber/Tupfile b/examples/bsp/gcc/libdecnumber/Tupfile index ff74081..7672a69 100644 --- a/examples/bsp/gcc/libdecnumber/Tupfile +++ b/examples/bsp/gcc/libdecnumber/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # libdecnumber - BID decimal floating-point (GCC 15.2.0) diff --git a/examples/bsp/gcc/libdecnumber/tup.config b/examples/bsp/gcc/libdecnumber/defaults.config similarity index 100% rename from examples/bsp/gcc/libdecnumber/tup.config rename to examples/bsp/gcc/libdecnumber/defaults.config diff --git a/examples/bsp/gcc/libiberty/Tupfile b/examples/bsp/gcc/libiberty/Tupfile index 18ae4a3..5378098 100644 --- a/examples/bsp/gcc/libiberty/Tupfile +++ b/examples/bsp/gcc/libiberty/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # ============================================================ # libiberty - GCC utility library diff --git a/examples/bsp/gcc/libiberty/tup.config b/examples/bsp/gcc/libiberty/defaults.config similarity index 100% rename from examples/bsp/gcc/libiberty/tup.config rename to examples/bsp/gcc/libiberty/defaults.config diff --git a/examples/bsp/gcc/mpc/Tupfile b/examples/bsp/gcc/mpc/Tupfile index 1bc5ab6..3f79c8c 100644 --- a/examples/bsp/gcc/mpc/Tupfile +++ b/examples/bsp/gcc/mpc/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # MPC 1.2.1 - generated files diff --git a/examples/bsp/gcc/mpc/tup.config b/examples/bsp/gcc/mpc/defaults.config similarity index 100% rename from examples/bsp/gcc/mpc/tup.config rename to examples/bsp/gcc/mpc/defaults.config diff --git a/examples/bsp/gcc/mpfr/Tupfile b/examples/bsp/gcc/mpfr/Tupfile index 60db22c..c2804c1 100644 --- a/examples/bsp/gcc/mpfr/Tupfile +++ b/examples/bsp/gcc/mpfr/Tupfile @@ -1,4 +1,5 @@ include_rules +: defaults.config |> ^ INSTALL %o^ cp %f %o |> tup.config # MPFR 4.1.0 - generated files diff --git a/examples/bsp/gcc/mpfr/tup.config b/examples/bsp/gcc/mpfr/defaults.config similarity index 100% rename from examples/bsp/gcc/mpfr/tup.config rename to examples/bsp/gcc/mpfr/defaults.config diff --git a/include/pup/cli/context.hpp b/include/pup/cli/context.hpp index e347533..73fbf21 100644 --- a/include/pup/cli/context.hpp +++ b/include/pup/cli/context.hpp @@ -6,7 +6,6 @@ #include "options.hpp" #include "pup/core/result.hpp" #include "pup/parser/ast.hpp" -#include "pup/parser/ignore.hpp" #include #include @@ -127,10 +126,6 @@ auto compute_build_scopes( [[nodiscard]] auto make_layout_options(Options const& opts) -> LayoutOptions; -/// Load .pupignore patterns (with built-in defaults) -[[nodiscard]] -auto load_ignore_list(ProjectLayout const& layout, bool verbose) -> parser::IgnoreList; - /// Context for clean commands struct CleanContext { std::filesystem::path root; diff --git a/include/pup/exec/scheduler.hpp b/include/pup/exec/scheduler.hpp index c5de101..bfad768 100644 --- a/include/pup/exec/scheduler.hpp +++ b/include/pup/exec/scheduler.hpp @@ -67,6 +67,7 @@ struct SchedulerOptions { bool dry_run = false; ///< Print commands without executing bool verbose = false; ///< Print commands as they run std::filesystem::path source_root = {}; ///< Source tree root (where Tupfile.ini lives) + std::filesystem::path config_root = {}; ///< Config tree root (where Tupfiles live, for 3-tree builds) std::filesystem::path output_root = {}; ///< Output tree root (where outputs/.pup go) std::optional timeout = {}; ///< Per-command timeout }; diff --git a/include/pup/graph/dag.hpp b/include/pup/graph/dag.hpp index 5d58d4b..64c754b 100644 --- a/include/pup/graph/dag.hpp +++ b/include/pup/graph/dag.hpp @@ -390,12 +390,15 @@ auto expand_instruction(Graph const& graph, NodeId cmd_id, PathCache& cache) -> /// Expand instruction with canonical path resolution for symlinked source trees. /// When source_root is provided, build-tree paths are computed relative to the /// canonical (physical) CWD instead of the logical CWD. +/// In 3-tree builds, config_root locates overlay files (like defaults.config) +/// that live alongside Tupfiles rather than in source_root. [[nodiscard]] auto expand_instruction( Graph const& graph, NodeId cmd_id, PathCache& cache, - std::filesystem::path const& source_root + std::filesystem::path const& source_root, + std::filesystem::path const& config_root = {} ) -> std::string; /// Expand instruction pattern (convenience overload, creates temporary cache) diff --git a/src/cli/cmd_build.cpp b/src/cli/cmd_build.cpp index 171f2f4..9a21359 100644 --- a/src/cli/cmd_build.cpp +++ b/src/cli/cmd_build.cpp @@ -1088,6 +1088,7 @@ auto build_single_variant( .dry_run = opts.dry_run, .verbose = opts.verbose, .source_root = ctx.layout().source_root, + .config_root = ctx.layout().config_root, .output_root = ctx.layout().output_root, }; diff --git a/src/cli/cmd_configure.cpp b/src/cli/cmd_configure.cpp index 59a66c1..9fc6cea 100644 --- a/src/cli/cmd_configure.cpp +++ b/src/cli/cmd_configure.cpp @@ -9,7 +9,6 @@ #include "pup/core/path_utils.hpp" #include "pup/exec/scheduler.hpp" #include "pup/graph/dag.hpp" -#include "pup/parser/ignore.hpp" #include #include @@ -44,73 +43,11 @@ auto install_config_file( return EXIT_SUCCESS; } -auto install_source_configs( - ProjectLayout const& layout, - std::string_view variant_name, - bool verbose, - pup::parser::IgnoreList const& ignore -) -> void -{ - if (layout.config_root == layout.output_root) { - return; - } - - auto config_canonical = std::filesystem::weakly_canonical(layout.config_root); - auto output_canonical = std::filesystem::weakly_canonical(layout.output_root); - auto count = 0; - auto ec = std::error_code {}; - - for (auto it = std::filesystem::recursive_directory_iterator(config_canonical, ec); - it != std::filesystem::recursive_directory_iterator(); - it.increment(ec)) { - if (ec) { - fprintf(stderr, "[%.*s] Warning: error scanning configs: %s\n", static_cast(variant_name.size()), variant_name.data(), ec.message().c_str()); - break; - } - - // Skip the output tree if it lives inside config_root (e.g., -B build) - if (it->is_directory() && it->path() == output_canonical) { - it.disable_recursion_pending(); - continue; - } - - // Skip directories matched by .pupignore - if (it->is_directory()) { - auto rel = std::filesystem::relative(it->path(), config_canonical); - if (ignore.is_ignored(rel)) { - it.disable_recursion_pending(); - continue; - } - } - - if (!it->is_regular_file() || it->path().filename() != "tup.config") { - continue; - } - - auto rel = std::filesystem::relative(it->path(), config_canonical); - if (rel == "tup.config") { - continue; - } - - auto dest = layout.output_root / rel; - std::filesystem::create_directories(dest.parent_path()); - std::filesystem::copy_file(it->path(), dest, std::filesystem::copy_options::overwrite_existing); - if (verbose) { - printf("[%.*s] Copied %s -> %s\n", static_cast(variant_name.size()), variant_name.data(), it->path().string().c_str(), dest.string().c_str()); - } - ++count; - } - if (count > 0) { - printf("[%.*s] Installed %d source config(s)\n", static_cast(variant_name.size()), variant_name.data(), count); - } -} - auto configure_single_variant( Options const& opts, std::string_view variant_name ) -> int { - // Discover layout once for steps 1 & 2 auto layout = discover_layout(make_layout_options(opts)); if (!layout) { fprintf(stderr, "[%.*s] Error: %s\n", static_cast(variant_name.size()), variant_name.data(), layout.error().message.c_str()); @@ -125,11 +62,7 @@ auto configure_single_variant( } } - // Step 2: Copy source subdir tup.config files to build tree - auto ignore = load_ignore_list(*layout, opts.verbose); - install_source_configs(*layout, variant_name, opts.verbose, ignore); - - // Step 3: Run config-generating rules + // Step 2: Run config-generating rules auto scopes = compute_build_scopes(opts, *layout); auto ctx_opts = BuildContextOptions { .verbose = opts.verbose, @@ -200,6 +133,7 @@ auto configure_single_variant( .dry_run = opts.dry_run, .verbose = opts.verbose, .source_root = ctx.layout().source_root, + .config_root = ctx.layout().config_root, .output_root = ctx.layout().output_root, }; diff --git a/src/cli/cmd_parse.cpp b/src/cli/cmd_parse.cpp index 183b5c0..79073bc 100644 --- a/src/cli/cmd_parse.cpp +++ b/src/cli/cmd_parse.cpp @@ -55,7 +55,7 @@ auto parse_single_variant(Options const& opts, std::string_view variant_name) -> for (auto id : commands) { if (ctx.graph().get_command_node(id)) { auto display_sv = pup::graph::get_display_str(ctx.graph().graph(), id); - auto cmd_sv = pup::graph::expand_instruction(ctx.graph().graph(), id, cache, ctx.layout().source_root); + auto cmd_sv = pup::graph::expand_instruction(ctx.graph().graph(), id, cache, ctx.layout().source_root, ctx.layout().config_root); auto label = display_sv.empty() ? cmd_sv : display_sv; printf("[%.*s] %.*s\n", static_cast(variant_name.size()), variant_name.data(), static_cast(label.size()), label.data()); } diff --git a/src/cli/context.cpp b/src/cli/context.cpp index b241c30..97ff14b 100644 --- a/src/cli/context.cpp +++ b/src/cli/context.cpp @@ -508,8 +508,6 @@ auto sort_dirs_by_depth(std::set const& available) -> std return dirs; } -} // namespace - auto load_ignore_list(ProjectLayout const& layout, bool verbose) -> pup::parser::IgnoreList { auto ignore = pup::parser::IgnoreList::with_defaults(); @@ -531,6 +529,8 @@ auto load_ignore_list(ProjectLayout const& layout, bool verbose) -> pup::parser: return ignore; } +} // namespace + auto make_layout_options(Options const& opts) -> LayoutOptions { auto layout_opts = LayoutOptions {}; diff --git a/src/exec/scheduler.cpp b/src/exec/scheduler.cpp index bca3163..5fc1f0c 100644 --- a/src/exec/scheduler.cpp +++ b/src/exec/scheduler.cpp @@ -821,7 +821,7 @@ auto Scheduler::build_job_list( } // Expand command from instruction pattern + operands - auto cmd_str = expand_instruction(graph.graph(), id, cache, impl_->options.source_root); + auto cmd_str = expand_instruction(graph.graph(), id, cache, impl_->options.source_root, impl_->options.config_root); auto display_str = std::string { get_display_str(graph.graph(), id) }; // Convert exported_vars from StringIds to strings diff --git a/src/graph/builder.cpp b/src/graph/builder.cpp index 9895645..6dfd2ab 100644 --- a/src/graph/builder.cpp +++ b/src/graph/builder.cpp @@ -157,6 +157,7 @@ struct PathTransformContext { std::string source_to_root; std::string current_dir_str; fs::path source_root; + fs::path config_root; fs::path output_root; fs::path canonical_cwd; // Canonical source CWD for symlink-safe path resolution }; @@ -173,6 +174,7 @@ auto make_transform_context(BuilderContext const& ctx) -> PathTransformContext .source_to_root = pup::compute_source_to_root(ctx.current_dir.generic_string()), .current_dir_str = ctx.current_dir.generic_string(), .source_root = ctx.options.source_root, + .config_root = ctx.options.config_root, .output_root = ctx.options.output_root, .canonical_cwd = std::move(canonical_cwd), }; @@ -222,6 +224,18 @@ auto transform_input_path( } } + // In 3-tree builds, files may live in config_root rather than source_root. + // Compute a path from the source cwd (where the command runs) to the config_root location. + if (!tc.config_root.empty() && tc.config_root != tc.source_root) { + auto config_path = tc.config_root / inp; + if (fs::exists(config_path)) { + auto source_cwd = tc.source_root / tc.current_dir_str; + auto canonical_source = fs::weakly_canonical(source_cwd); + auto canonical_config = fs::weakly_canonical(config_path); + return canonical_config.lexically_relative(canonical_source).generic_string(); + } + } + // Source file or not found - use path as-is return pup::make_source_relative(inp, tc.source_to_root, tc.current_dir_str); } @@ -2099,6 +2113,15 @@ auto resolve_input_node( return walk_to_file_node(*ctx.graph, SOURCE_ROOT_ID, normalized_path, NodeType::File); } + // In 3-tree builds, files may live in config_root (alongside Tupfiles) rather than + // source_root. Check config_root as a fallback for source file resolution. + if (!ctx.options.config_root.empty() && ctx.options.config_root != ctx.options.source_root) { + auto config_path = ctx.options.config_root / normalized_path; + if (fs::exists(config_path)) { + return walk_to_file_node(*ctx.graph, SOURCE_ROOT_ID, normalized_path, NodeType::File); + } + } + // Check if file exists in build directory (e.g., tup.config, or already-generated files) auto build_path = ctx.options.output_root / normalized_path; if (fs::exists(build_path)) { @@ -2503,6 +2526,7 @@ auto resolve_deferred_order_only_edges( .source_to_root = pup::compute_source_to_root(source_dir_str), .current_dir_str = source_dir_str, .source_root = state.options.source_root, + .config_root = state.options.config_root, .output_root = state.options.output_root, .canonical_cwd = std::move(canonical_cwd), }; diff --git a/src/graph/dag.cpp b/src/graph/dag.cpp index 5be39f9..50b6be2 100644 --- a/src/graph/dag.cpp +++ b/src/graph/dag.cpp @@ -854,7 +854,8 @@ auto expand_instruction( Graph const& graph, NodeId cmd_id, PathCache& cache, - std::filesystem::path const& source_root + std::filesystem::path const& source_root, + std::filesystem::path const& config_root ) -> std::string { auto const* cmd = get_command_node(graph, cmd_id); @@ -873,6 +874,14 @@ auto expand_instruction( auto abs = std::filesystem::weakly_canonical(source_root / full); return abs.lexically_relative(canonical_cwd).generic_string(); } + // In 3-tree builds, files may live in config_root rather than source_root. + // Check config_root and compute a canonical relative path from source CWD. + if (!config_root.empty() && config_root != source_root + && !std::filesystem::exists(source_root / full) + && std::filesystem::exists(config_root / full)) { + auto canonical_config = std::filesystem::weakly_canonical(config_root / full); + return canonical_config.lexically_relative(canonical_cwd).generic_string(); + } return pup::make_source_relative(full, source_to_root, source_dir); }); } diff --git a/test/unit/test_e2e.cpp b/test/unit/test_e2e.cpp index 66ab96d..5e0ecc6 100644 --- a/test/unit/test_e2e.cpp +++ b/test/unit/test_e2e.cpp @@ -3541,13 +3541,14 @@ SCENARIO("configure with --config followed by build works", "[e2e][configure][bu } } -SCENARIO("configure copies source subdir configs to build tree", "[e2e][configure]") +SCENARIO("configure installs subdir configs via Tupfile copy rules", "[e2e][configure]") { - GIVEN("a project with a subdir tup.config") + GIVEN("a project with a subdir config and a copy rule") { auto f = E2EFixture { "simple_c" }; f.mkdir("sub"); - f.write_file("sub/tup.config", "CONFIG_SUB_VAR=from_sub\n"); + f.write_file("sub/defaults.config", "CONFIG_SUB_VAR=from_sub\n"); + f.write_file("sub/Tupfile", ": defaults.config |> cp %f %o |> tup.config\n"); f.write_file("root.config", "CONFIG_ROOT_VAR=from_root\n"); REQUIRE(f.init().success()); @@ -3565,7 +3566,7 @@ SCENARIO("configure copies source subdir configs to build tree", "[e2e][configur REQUIRE(content.find("CONFIG_ROOT_VAR=from_root") != std::string::npos); } - THEN("subdir config is copied to build/sub/tup.config") + THEN("subdir config is produced by copy rule") { REQUIRE(f.exists("build/sub/tup.config")); auto content = f.read_file("build/sub/tup.config"); @@ -3575,46 +3576,15 @@ SCENARIO("configure copies source subdir configs to build tree", "[e2e][configur } } -SCENARIO("configure skips ignored directories when copying configs", "[e2e][configure]") -{ - GIVEN("a project with subdir configs and a .pupignore") - { - auto f = E2EFixture { "simple_c" }; - f.mkdir("included"); - f.write_file("included/tup.config", "CONFIG_INC=yes\n"); - f.mkdir("ignored_dir"); - f.write_file("ignored_dir/tup.config", "CONFIG_IGN=yes\n"); - f.write_file(".pupignore", "ignored_dir\n"); - f.write_file("root.config", "CONFIG_ROOT=1\n"); - REQUIRE(f.init().success()); - - WHEN("configure is run with -B") - { - auto result = f.pup({ "configure", "--config", "root.config", "-B", "build" }); - - THEN("included subdir config is copied") - { - INFO("stdout: " << result.stdout_output); - INFO("stderr: " << result.stderr_output); - REQUIRE(result.success()); - REQUIRE(f.exists("build/included/tup.config")); - } - - THEN("ignored subdir config is NOT copied") - { - REQUIRE(result.success()); - REQUIRE_FALSE(f.exists("build/ignored_dir/tup.config")); - } - } - } -} - SCENARIO("configure --config + subdir configs + build uses scoped merge", "[e2e][configure][scoped-config]") { - GIVEN("a project with root config and subdir tup.config in source tree") + GIVEN("a project with root config and subdir config via copy rule") { auto f = E2EFixture { "scoped_config" }; - f.write_file("sub/tup.config", "CONFIG_SUB_VAR=from_sub\n"); + f.write_file("sub/defaults.config", "CONFIG_SUB_VAR=from_sub\n"); + f.write_file("sub/Tupfile", ": defaults.config |> cp %f %o |> tup.config\n" + ": |> echo \"@(SUB_VAR)\" > %o |> sub.txt\n" + ": |> echo \"@(ROOT_VAR)\" > %o |> root_from_sub.txt\n"); f.write_file("root.config", "CONFIG_ROOT_VAR=from_root\n"); REQUIRE(f.init().success()); @@ -3637,37 +3607,16 @@ SCENARIO("configure --config + subdir configs + build uses scoped merge", "[e2e] } } -SCENARIO("configure skips subdir config copy for in-tree", "[e2e][configure]") +SCENARIO("configure handles mixed copy-rule + auto-gen configs", "[e2e][configure]") { - GIVEN("a project with -C matching output root") - { - auto f = E2EFixture { "scoped_config" }; - f.write_file("tup.config", "CONFIG_ROOT_VAR=from_root\n"); - REQUIRE(f.init().success()); - - WHEN("configure is run without -B") - { - auto result = f.pup({ "configure" }); - - THEN("configure succeeds without copying configs") - { - INFO("stdout: " << result.stdout_output); - INFO("stderr: " << result.stderr_output); - REQUIRE(result.success()); - REQUIRE(result.stdout_output.find("source config") == std::string::npos); - } - } - } -} - -SCENARIO("configure handles mixed static + auto-gen configs", "[e2e][configure]") -{ - GIVEN("a project with a static subdir config and auto-gen config rules") + GIVEN("a project with a copy-rule subdir config and auto-gen config rules") { auto f = E2EFixture { "configure_cmd" }; f.mkdir("build"); f.write_file("build/tup.config", "CONFIG_MACHINE=board-xyz\n"); - f.write_file("sub/tup.config", "CONFIG_STATIC_VAR=static_value\n"); + f.mkdir("sub"); + f.write_file("sub/defaults.config", "CONFIG_STATIC_VAR=static_value\n"); + f.write_file("sub/Tupfile", ": defaults.config |> cp %f %o |> tup.config\n"); REQUIRE(f.init().success()); WHEN("configure is run with -B") @@ -3682,7 +3631,7 @@ SCENARIO("configure handles mixed static + auto-gen configs", "[e2e][configure]" REQUIRE(f.exists("build/configs/tup.config")); } - THEN("static subdir config is copied") + THEN("copy-rule subdir config is produced") { REQUIRE(f.exists("build/sub/tup.config")); auto content = f.read_file("build/sub/tup.config");