From 5b7f983b3912a8d0fbeaf22f7fe0e2c5f1e0ff01 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 4 Oct 2026 12:56:13 +0200 Subject: Generate a dependency tree for rake --- Rakefile | 28 +---- gcc/gcc/elna-module-loader.cc | 192 +++++++++++++++++++++++++++++++++- gcc/gcc/elna1.cc | 29 ++++- gcc/lang-specs.h | 11 +- gcc/lang.opt | 32 ++++++ include/elna/gcc/elna-module-loader.h | 27 ++++- rakelib/compiler.rake | 32 ++++++ rakelib/doc.rake | 1 - rakelib/gcc.rake | 1 - 9 files changed, 318 insertions(+), 35 deletions(-) create mode 100644 rakelib/compiler.rake diff --git a/Rakefile b/Rakefile index ff537ec..a5e0ebd 100644 --- a/Rakefile +++ b/Rakefile @@ -3,37 +3,13 @@ # obtain one at https://mozilla.org/MPL/2.0/. -} # frozen_string_literal: true -require 'pathname' require 'rake/clean' -CLEAN.include 'build/gcc' CLOBBER.include 'build' -task default: :source - -task source: ['source/main.elna', 'build/gcc/elna'] do |t| +task default: 'source/main.elna' +task default: 'build/gcc/elna' do |t| sources, compiler = t.prerequisites.partition { |f| f.end_with? '.elna' } sh *compiler, '--parse', *sources end - -rule(/gcc\/.+\.o$/ => ->(file) { - source = Pathname.new('source') + - Pathname.new(file).relative_path_from('build/gcc').sub_ext('.elna') - - ['build/host/install/bin/gelna', source] -}) do |t| - Pathname.new(t.name).dirname.mkpath - sources, compiler = t.prerequisites.partition { |source| source.end_with? '.elna' } - - sh *compiler, '-c', '-O0', '-g', '-I', 'source', '-o', t.name, *sources -end - -file 'build/gcc/elna' => FileList['source/**/*.elna'].reject { |file| - file != file.downcase -}.map { |file| - Pathname.new('build/gcc') + - Pathname.new(file).relative_path_from('source').sub_ext('.o') -} do |t| - sh 'build/host/install/bin/gcc', '-o', t.name, *t.prerequisites -end diff --git a/gcc/gcc/elna-module-loader.cc b/gcc/gcc/elna-module-loader.cc index 5852bbe..c32b614 100644 --- a/gcc/gcc/elna-module-loader.cc +++ b/gcc/gcc/elna-module-loader.cc @@ -18,14 +18,171 @@ along with GCC; see the file COPYING3. If not see #include "elna/gcc/elna-module-loader.h" #include +#include +#include +#include #include "elna/gcc/elna-builtins.h" #include "elna/gcc/elna-diagnostic.h" #include "elna/gcc/elna-generic.h" +#include "options.h" + namespace elna::gcc { std::vector elna_include_dirs; + dependency_options elna_deps_options; + + std::string munge_make_string(const std::string_view str, const bool is_target) + { + std::string result; + result.reserve(str.size() * 2); + size_t slashes = 0; + + for (const char character : str) + { + switch (character) + { + case '\\': + slashes++; + break; + case '$': + result.push_back('$'); + slashes = 0; + break; + case ' ': + case '\t': + for (size_t i = 0; i <= slashes; i++) + { + result.push_back('\\'); + } + slashes = 0; + break; + case '#': + result.push_back('\\'); + slashes = 0; + break; + case ':': + if (is_target) + { + result.push_back('\\'); + } + slashes = 0; + break; + default: + slashes = 0; + break; + } + result.push_back(character); + } + return result; + } + + static void write_dependency_name(std::ostream& stream, const std::string_view name, + size_t& column, const size_t max_column = 72) + { + const size_t name_length = name.size(); + + if (column > 0) + { + if (max_column > 0 && column + 1 + name_length > max_column) + { + stream << " \\\n "; + column = 1; + } + else + { + stream << ' '; + column++; + } + } + stream << name; + column += name_length; + } + + static void emit_dependencies(const std::filesystem::path& input_file, + const std::vector& dependencies) + { + std::string target_name; + + if (!elna_deps_options.targets.empty()) + { + for (size_t i = 0; i < elna_deps_options.targets.size(); i++) + { + if (i > 0) + { + target_name += ' '; + } + target_name += elna_deps_options.targets[i]; + } + } + else + { + target_name = munge_make_string(input_file.stem().string() + ".o", true); + } + + std::ostringstream buffer; + size_t column = 0; + + write_dependency_name(buffer, target_name, column); + buffer << ':'; + column++; + + write_dependency_name(buffer, munge_make_string(input_file.string(), false), column); + + std::unordered_set seen; + seen.insert(input_file.string()); + + std::vector phony_deps; + + for (const auto& dep : dependencies) + { + const std::string dep_str = dep.string(); + if (seen.insert(dep_str).second) + { + write_dependency_name(buffer, munge_make_string(dep_str, false), column); + if (elna_deps_options.phony) + { + phony_deps.push_back(dep_str); + } + } + } + buffer << '\n'; + + if (elna_deps_options.phony) + { + for (const auto& dep_str : phony_deps) + { + buffer << munge_make_string(dep_str, false) << ":\n"; + } + } + + if (!elna_deps_options.output_file.empty()) + { + std::ofstream file(elna_deps_options.output_file); + if (!file.is_open()) + { + fatal_error(UNKNOWN_LOCATION, "Cannot open dependency file %s: %m", + elna_deps_options.output_file.c_str()); + } + file << buffer.str(); + } + else if (elna_deps_options.only_dependencies) + { + fputs(buffer.str().c_str(), stdout); + } + else + { + const std::filesystem::path default_file = input_file.stem().replace_extension(".d"); + std::ofstream file(default_file); + if (!file.is_open()) + { + fatal_error(UNKNOWN_LOCATION, "Cannot open dependency file %s: %m", + default_file.string().c_str()); + } + file << buffer.str(); + } + } module_loader::module_loader(const std::shared_ptr& symbols) : symbols(symbols) @@ -62,6 +219,7 @@ namespace elna::gcc inform(UNKNOWN_LOCATION, " %s", candidate.native().c_str()); } } + this->m_dependencies.push_back(found.front()); return std::filesystem::weakly_canonical(found.front()); } @@ -85,12 +243,34 @@ namespace elna::gcc void compile_files(const char *const *filenames, unsigned int count) { + if (verbose_flag) + { + for (const auto& include_dir : elna_include_dirs) + { + if (!std::filesystem::exists(include_dir)) + { + std::cerr << "ignoring nonexistent directory \"" << include_dir << "\"\n"; + } + } + std::cerr << "#include \"...\" search starts here:\n"; + std::cerr << "#include <...> search starts here:\n"; + for (const auto& include_dir : elna_include_dirs) + { + if (std::filesystem::exists(include_dir)) + { + std::cerr << ' ' << include_dir << '\n'; + } + } + std::cerr << "End of search list.\n"; + } + const boot::target_info target = get_host_target(); boot::dependency_state> state{ builtin_symbol_table(), target }; module_loader loader{ state.custom }; for (unsigned int i = 0; i < count; i++) { + loader.clear_dependencies(); const std::filesystem::path key = std::filesystem::weakly_canonical(filenames[i]); boot::dependency result = state.compile(key, loader, target); @@ -99,7 +279,17 @@ namespace elna::gcc report_errors(diagnostics); } - if (result.value != nullptr && result.errors.empty()) + if (elna_deps_options.enabled && result.errors.empty()) + { + emit_dependencies(filenames[i], loader.dependencies()); + } + + const bool should_generate_code = !flag_syntax_only + && !elna_deps_options.only_dependencies + && result.value != nullptr + && result.errors.empty(); + + if (should_generate_code) { linemap_add(line_table, LC_ENTER, 0, ggc_strdup(key.native().c_str()), 1); generic_visitor visitor{ state.custom, state.find(key)->second, target }; diff --git a/gcc/gcc/elna1.cc b/gcc/gcc/elna1.cc index 2c43178..35101f1 100644 --- a/gcc/gcc/elna1.cc +++ b/gcc/gcc/elna1.cc @@ -167,8 +167,35 @@ static bool elna_langhook_handle_option( case OPT_I: elna::gcc::elna_include_dirs.emplace_back(arg); return true; - default: + case OPT_M: + case OPT_MM: + elna::gcc::elna_deps_options.enabled = true; + elna::gcc::elna_deps_options.only_dependencies = true; + return true; + case OPT_MD: + case OPT_MMD: + elna::gcc::elna_deps_options.enabled = true; + if (elna::gcc::elna_deps_options.output_file.empty() && arg != nullptr) + { + elna::gcc::elna_deps_options.output_file = arg; + } + return true; + case OPT_MF: + elna::gcc::elna_deps_options.enabled = true; + elna::gcc::elna_deps_options.output_file = arg; return true; + case OPT_MP: + elna::gcc::elna_deps_options.phony = true; + return true; + case OPT_MT: + elna::gcc::elna_deps_options.targets.emplace_back(arg); + return true; + case OPT_MQ: + elna::gcc::elna_deps_options.targets.push_back( + elna::gcc::munge_make_string(arg, true)); + return true; + default: + return false; } } diff --git a/gcc/lang-specs.h b/gcc/lang-specs.h index 5ef06e9..48c3fed 100644 --- a/gcc/lang-specs.h +++ b/gcc/lang-specs.h @@ -21,8 +21,11 @@ along with GCC; see the file COPYING3. If not see "elna1 %i \ %{!Q:-quiet} " DUMPS_OPTIONS("") " %{m*} %{aux-info*} \ %{g*} %{O*} %{W*&pedantic*} %{w} %{std*&ansi&trigraphs} \ - %{pg:-p} %{p} %{f*} %{undef} %{I*} \ - %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}} \ - %{fsyntax-only:-o %j} %{-param*} \ - %{!fsyntax-only:%(invoke_as)}", + %{pg:-p} %{p} %{f*} %{undef} %{I*} %{v} \ + %{MD:-MD %{!o:%b.d}%{o*:%.d%*}} %{MMD:-MMD %{!o:%b.d}%{o*:%.d%*}} \ + %{M} %{MM} %{MF*} %{MP} %{MQ*} %{MT*} \ + %{!M:%{!MM:%{!MT:%{!MQ:%{MD|MMD:%{o*:-MQ %*}}}}}} \ + %{!fsyntax-only:%{!M:%{!MM:%{S:%W{o*}%{!o*:-o %w%b.s}}}}} \ + %{fsyntax-only|M|MM:-o %j} %{-param*} \ + %{!fsyntax-only:%{!M:%{!MM:%(invoke_as)}}}", nullptr, 0, 0}, diff --git a/gcc/lang.opt b/gcc/lang.opt index e8e5920..ffa84a0 100644 --- a/gcc/lang.opt +++ b/gcc/lang.opt @@ -25,3 +25,35 @@ Elna I Elna Joined Separate MissingArgError(missing path after %qs) -I Add to the end of the main include path. + +M +Elna +; Documented in C + +MD +Elna Separate NoDriverArg +; Documented in C + +MF +Elna Joined Separate MissingArgError(missing filename after %qs) +; Documented in C + +MM +Elna +; Documented in C + +MMD +Elna Separate NoDriverArg +; Documented in C + +MP +Elna +; Documented in C + +MQ +Elna Joined Separate MissingArgError(missing target after %qs) +; Documented in C + +MT +Elna Joined Separate MissingArgError(missing target after %qs) +; Documented in C diff --git a/include/elna/gcc/elna-module-loader.h b/include/elna/gcc/elna-module-loader.h index 5f5aab6..c49b880 100644 --- a/include/elna/gcc/elna-module-loader.h +++ b/include/elna/gcc/elna-module-loader.h @@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see #include #include #include +#include #include #include "config.h" @@ -35,6 +36,19 @@ namespace elna::gcc // the module loader when resolving import paths. extern std::vector elna_include_dirs; + struct dependency_options + { + bool enabled = false; + bool only_dependencies = false; + bool phony = false; + std::string output_file; + std::vector targets; + }; + + extern dependency_options elna_deps_options; + + std::string munge_make_string(std::string_view str, bool is_target); + /** * Module loader tying the boot compilation pipeline to the GCC driver. * @@ -46,10 +60,21 @@ namespace elna::gcc class module_loader { const std::shared_ptr symbols; + std::vector m_dependencies; public: explicit module_loader(const std::shared_ptr& symbols); + const std::vector& dependencies() const + { + return this->m_dependencies; + } + + void clear_dependencies() + { + this->m_dependencies.clear(); + } + /** * Searches the include directories for \p relative. * @@ -61,7 +86,7 @@ namespace elna::gcc * * \return The resolved module path, canonical if found. */ - static std::filesystem::path resolve(const std::filesystem::path& relative, + std::filesystem::path resolve(const std::filesystem::path& relative, const boot::source_position& position, const std::filesystem::path& module); /** diff --git a/rakelib/compiler.rake b/rakelib/compiler.rake new file mode 100644 index 0000000..f443407 --- /dev/null +++ b/rakelib/compiler.rake @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. -} +# frozen_string_literal: true + +require 'rake/loaders/makefile' +require 'rake/clean' + +CLEAN.include 'build/gcc' + +Rake.application.add_loader('d', Rake::MakefileLoader.new) +FileList['build/gcc/**/*.d'].each { |file| import file } + +rule(/gcc\/.+\.o$/ => ->(file) { + source = Pathname.new('source') + + Pathname.new(file).relative_path_from('build/gcc').sub_ext('.elna') + + [source, 'build/host/install/bin/gelna'] +}) do |t| + Pathname.new(t.name).dirname.mkpath + + sh t.prerequisites.last, '-c', '-MD', '-MP', '-O0', '-g', '-I', 'source', '-o', t.name, t.source.to_s +end + +file 'build/gcc/elna' => FileList['source/**/*.elna'].reject { |file| + file != file.downcase +}.map { |file| + Pathname.new('build/gcc') + + Pathname.new(file).relative_path_from('source').sub_ext('.o') +} do |t| + sh 'build/host/install/bin/gcc', '-o', t.name, *t.prerequisites +end diff --git a/rakelib/doc.rake b/rakelib/doc.rake index 7838871..5f40107 100644 --- a/rakelib/doc.rake +++ b/rakelib/doc.rake @@ -3,7 +3,6 @@ # obtain one at https://mozilla.org/MPL/2.0/. # frozen_string_literal: true -require 'pathname' require 'rake/clean' CLEAN.include 'build/doc' diff --git a/rakelib/gcc.rake b/rakelib/gcc.rake index 30175eb..7ee9f5c 100644 --- a/rakelib/gcc.rake +++ b/rakelib/gcc.rake @@ -7,7 +7,6 @@ require 'fileutils' require 'uri' require 'net/http' require 'open3' -require 'pathname' # Use Homebrew GCC if available (it uses libstdc++, avoiding # macOS-specific libc++ incompatibilities in GCC's own sources). -- cgit v1.2.3