aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Rakefile28
-rw-r--r--gcc/gcc/elna-module-loader.cc192
-rw-r--r--gcc/gcc/elna1.cc29
-rw-r--r--gcc/lang-specs.h11
-rw-r--r--gcc/lang.opt32
-rw-r--r--include/elna/gcc/elna-module-loader.h27
-rw-r--r--rakelib/compiler.rake32
-rw-r--r--rakelib/doc.rake1
-rw-r--r--rakelib/gcc.rake1
9 files changed, 318 insertions, 35 deletions
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 <fstream>
+#include <iostream>
+#include <sstream>
+#include <unordered_set>
#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<std::string> 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<std::filesystem::path>& 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<std::string> seen;
+ seen.insert(input_file.string());
+
+ std::vector<std::string> 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<symbol_table>& 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<std::shared_ptr<symbol_table>> 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 <dir> Add <dir> 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 <filesystem>
#include <memory>
#include <string>
+#include <string_view>
#include <vector>
#include "config.h"
@@ -35,6 +36,19 @@ namespace elna::gcc
// the module loader when resolving import paths.
extern std::vector<std::string> elna_include_dirs;
+ struct dependency_options
+ {
+ bool enabled = false;
+ bool only_dependencies = false;
+ bool phony = false;
+ std::string output_file;
+ std::vector<std::string> 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<symbol_table> symbols;
+ std::vector<std::filesystem::path> m_dependencies;
public:
explicit module_loader(const std::shared_ptr<symbol_table>& symbols);
+ const std::vector<std::filesystem::path>& 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).