|
16 | 16 |
|
17 | 17 | #include <vix/cli/build/BuildGraph.hpp> |
18 | 18 | #include <vix/cli/build/CompileCommands.hpp> |
| 19 | +#include <vix/cli/build/BuildNinja.hpp> |
19 | 20 |
|
20 | 21 | #include <algorithm> |
21 | 22 | #include <cstdint> |
@@ -476,6 +477,129 @@ namespace vix::cli::build |
476 | 477 |
|
477 | 478 | return out; |
478 | 479 | } |
| 480 | + |
| 481 | + static BuildNodeKind node_kind_from_ninja_edge_output( |
| 482 | + const NinjaEdge &edge, |
| 483 | + const fs::path &path) |
| 484 | + { |
| 485 | + const std::string ext = path.extension().string(); |
| 486 | + |
| 487 | + if (edge.kind == NinjaEdgeKind::Archive) |
| 488 | + return BuildNodeKind::Library; |
| 489 | + |
| 490 | + if (edge.kind == NinjaEdgeKind::Link) |
| 491 | + { |
| 492 | + if (ext == ".a" || |
| 493 | + ext == ".so" || |
| 494 | + ext == ".dylib" || |
| 495 | + ext == ".dll" || |
| 496 | + ext == ".lib") |
| 497 | + { |
| 498 | + return BuildNodeKind::Library; |
| 499 | + } |
| 500 | + |
| 501 | + return BuildNodeKind::Executable; |
| 502 | + } |
| 503 | + |
| 504 | + if (edge.kind == NinjaEdgeKind::Copy || |
| 505 | + edge.kind == NinjaEdgeKind::Install) |
| 506 | + { |
| 507 | + return BuildNodeKind::Config; |
| 508 | + } |
| 509 | + |
| 510 | + return BuildNodeKind::Unknown; |
| 511 | + } |
| 512 | + |
| 513 | + static BuildNodeKind node_kind_from_ninja_input(const fs::path &path) |
| 514 | + { |
| 515 | + const std::string ext = path.extension().string(); |
| 516 | + |
| 517 | + if (ext == ".o" || ext == ".obj") |
| 518 | + return BuildNodeKind::Object; |
| 519 | + |
| 520 | + if (ext == ".a" || |
| 521 | + ext == ".so" || |
| 522 | + ext == ".dylib" || |
| 523 | + ext == ".dll" || |
| 524 | + ext == ".lib") |
| 525 | + { |
| 526 | + return BuildNodeKind::Library; |
| 527 | + } |
| 528 | + |
| 529 | + if (is_source_extension(ext)) |
| 530 | + return BuildNodeKind::Source; |
| 531 | + |
| 532 | + if (is_header_extension(ext)) |
| 533 | + return BuildNodeKind::Header; |
| 534 | + |
| 535 | + return BuildNodeKind::Config; |
| 536 | + } |
| 537 | + |
| 538 | + static BuildTaskKind build_task_kind_from_ninja_edge_kind(NinjaEdgeKind kind) |
| 539 | + { |
| 540 | + switch (kind) |
| 541 | + { |
| 542 | + case NinjaEdgeKind::Archive: |
| 543 | + return BuildTaskKind::Archive; |
| 544 | + case NinjaEdgeKind::Link: |
| 545 | + return BuildTaskKind::Link; |
| 546 | + case NinjaEdgeKind::Copy: |
| 547 | + return BuildTaskKind::Copy; |
| 548 | + case NinjaEdgeKind::Install: |
| 549 | + return BuildTaskKind::Copy; |
| 550 | + case NinjaEdgeKind::Utility: |
| 551 | + return BuildTaskKind::Generate; |
| 552 | + case NinjaEdgeKind::Compile: |
| 553 | + return BuildTaskKind::Compile; |
| 554 | + case NinjaEdgeKind::Unknown: |
| 555 | + default: |
| 556 | + return BuildTaskKind::Unknown; |
| 557 | + } |
| 558 | + } |
| 559 | + |
| 560 | + static std::string ninja_task_id_for_edge(const NinjaEdge &edge) |
| 561 | + { |
| 562 | + std::uint64_t h = FNV_OFFSET; |
| 563 | + |
| 564 | + h = fnv_mix_string(h, "ninja:"); |
| 565 | + h = fnv_mix_string(h, to_string(edge.kind)); |
| 566 | + h = fnv_mix_string(h, edge.rule); |
| 567 | + |
| 568 | + for (const fs::path &output : edge.outputs) |
| 569 | + h = fnv_mix_string(h, normalize_path_string(output)); |
| 570 | + |
| 571 | + return "ninja:" + hex64(h); |
| 572 | + } |
| 573 | + |
| 574 | + static std::string command_hash_for_argv( |
| 575 | + const std::vector<std::string> &command) |
| 576 | + { |
| 577 | + std::uint64_t h = FNV_OFFSET; |
| 578 | + |
| 579 | + h = fnv_mix_string(h, "command:"); |
| 580 | + |
| 581 | + for (const std::string &arg : command) |
| 582 | + { |
| 583 | + h = fnv_mix_string(h, arg); |
| 584 | + h = fnv_mix_string(h, "\0"); |
| 585 | + } |
| 586 | + |
| 587 | + return hex64(h); |
| 588 | + } |
| 589 | + |
| 590 | + static bool should_import_ninja_edge(const NinjaEdge &edge) |
| 591 | + { |
| 592 | + if (!edge.valid()) |
| 593 | + return false; |
| 594 | + |
| 595 | + if (edge.kind == NinjaEdgeKind::Compile) |
| 596 | + return false; |
| 597 | + |
| 598 | + if (edge.kind == NinjaEdgeKind::Unknown) |
| 599 | + return false; |
| 600 | + |
| 601 | + return true; |
| 602 | + } |
479 | 603 | } // namespace |
480 | 604 |
|
481 | 605 | bool BuildGraphConfig::valid() const |
@@ -729,6 +853,114 @@ namespace vix::cli::build |
729 | 853 | return imported; |
730 | 854 | } |
731 | 855 |
|
| 856 | + std::size_t BuildGraph::load_ninja_build(const fs::path &path) |
| 857 | + { |
| 858 | + const auto ninjaBuild = read_build_ninja(path); |
| 859 | + |
| 860 | + if (!ninjaBuild) |
| 861 | + return 0; |
| 862 | + |
| 863 | + std::size_t imported = 0; |
| 864 | + |
| 865 | + for (const NinjaEdge &edge : ninjaBuild->edges) |
| 866 | + { |
| 867 | + if (!should_import_ninja_edge(edge)) |
| 868 | + continue; |
| 869 | + |
| 870 | + const BuildTaskKind taskKind = |
| 871 | + build_task_kind_from_ninja_edge_kind(edge.kind); |
| 872 | + |
| 873 | + if (taskKind == BuildTaskKind::Unknown) |
| 874 | + continue; |
| 875 | + |
| 876 | + BuildTask task; |
| 877 | + task.id = ninja_task_id_for_edge(edge); |
| 878 | + task.kind = taskKind; |
| 879 | + task.state = BuildTaskState::Pending; |
| 880 | + task.workingDirectory = ninjaBuild->directory; |
| 881 | + |
| 882 | + /* |
| 883 | + * Do not expand Ninja rule commands here. |
| 884 | + * |
| 885 | + * build.ninja is already a complete execution graph. For now we import |
| 886 | + * the DAG structure only. Execution remains delegated to Ninja/CMake until |
| 887 | + * Vix has a full Ninja variable expander and target-aware executor. |
| 888 | + */ |
| 889 | + task.command = { |
| 890 | + "ninja", |
| 891 | + "-C", |
| 892 | + ninjaBuild->directory.string(), |
| 893 | + edge.primary_output().string()}; |
| 894 | + |
| 895 | + task.commandHash = command_hash_for_argv(task.command); |
| 896 | + |
| 897 | + for (const fs::path &input : edge.explicitInputs) |
| 898 | + { |
| 899 | + const BuildNodeKind inputKind = node_kind_from_ninja_input(input); |
| 900 | + |
| 901 | + BuildNode inputNode = make_file_build_node(inputKind, input); |
| 902 | + |
| 903 | + /* |
| 904 | + * Keep Ninja import cheap. |
| 905 | + * scan_project() and load_dependency_files() already hash real project |
| 906 | + * inputs where needed. Ninja edges may reference many generated files. |
| 907 | + */ |
| 908 | + inputNode.hash.clear(); |
| 909 | + |
| 910 | + add_node(inputNode); |
| 911 | + task.add_input(inputNode.id); |
| 912 | + } |
| 913 | + |
| 914 | + for (const fs::path &input : edge.implicitInputs) |
| 915 | + { |
| 916 | + const BuildNodeKind inputKind = node_kind_from_ninja_input(input); |
| 917 | + |
| 918 | + BuildNode inputNode = make_file_build_node(inputKind, input); |
| 919 | + inputNode.hash.clear(); |
| 920 | + |
| 921 | + add_node(inputNode); |
| 922 | + task.add_input(inputNode.id); |
| 923 | + } |
| 924 | + |
| 925 | + for (const fs::path &input : edge.orderOnlyInputs) |
| 926 | + { |
| 927 | + const BuildNodeKind inputKind = node_kind_from_ninja_input(input); |
| 928 | + |
| 929 | + BuildNode inputNode = make_file_build_node(inputKind, input); |
| 930 | + inputNode.hash.clear(); |
| 931 | + |
| 932 | + add_node(inputNode); |
| 933 | + task.add_input(inputNode.id); |
| 934 | + } |
| 935 | + |
| 936 | + for (const fs::path &output : edge.outputs) |
| 937 | + { |
| 938 | + const BuildNodeKind outputKind = |
| 939 | + node_kind_from_ninja_edge_output(edge, output); |
| 940 | + |
| 941 | + if (outputKind == BuildNodeKind::Unknown) |
| 942 | + continue; |
| 943 | + |
| 944 | + BuildNode outputNode = make_file_build_node(outputKind, output); |
| 945 | + outputNode.hash.clear(); |
| 946 | + |
| 947 | + for (const auto &inputId : task.inputs) |
| 948 | + outputNode.add_dependency(inputId); |
| 949 | + |
| 950 | + add_node(outputNode); |
| 951 | + task.add_output(outputNode.id); |
| 952 | + } |
| 953 | + |
| 954 | + if (task.outputs.empty()) |
| 955 | + continue; |
| 956 | + |
| 957 | + add_task(task); |
| 958 | + ++imported; |
| 959 | + } |
| 960 | + |
| 961 | + return imported; |
| 962 | + } |
| 963 | + |
732 | 964 | void BuildGraph::load_dependency_files() |
733 | 965 | { |
734 | 966 | for (auto &kv : tasks_) |
|
0 commit comments