From 3d7bb108c4bb895c7c94ea127b490ced3d54f16f Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Tue, 27 Aug 2024 02:42:50 -0400 Subject: [PATCH] The basic skeleton is in place. I have to fix the symbol searching... --- .gitmodules | 3 + Alepha | 1 + Makefile | 7 ++ bake.cc | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100644 .gitmodules create mode 160000 Alepha create mode 100644 Makefile create mode 100644 bake.cc diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ed65794 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Alepha"] + path = Alepha + url = gitea.nerdland.it.cx:Alepha/Alepha diff --git a/Alepha b/Alepha new file mode 160000 index 0000000..d4dfe9f --- /dev/null +++ b/Alepha @@ -0,0 +1 @@ +Subproject commit d4dfe9f90f3ea6f4b9a7a3f4b6613e1025d8e997 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2a3a1c --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +CPPFLAGS+= -I . +CXXFLAGS+= -std=c++2c +LDFLAGS+= -L ./Alepha/ -Wl,-rpath=/home/adam/proj/bake/Alepha +LDLIBS+= -lalepha +CXX=g++-14 + +all: bake diff --git a/bake.cc b/bake.cc new file mode 100644 index 0000000..80f6ae3 --- /dev/null +++ b/bake.cc @@ -0,0 +1,333 @@ +static_assert( __cplusplus > 2023'00 ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if 1 + +namespace +{ + namespace C + { + const bool debug= false; + } + + [[noreturn]] void + unimpl( const std::string &what ) + { + std::cerr << Alepha::StartWrap{ unsigned( Alepha::getConsoleWidth() ) }; + std::cerr << "Internal Error. Unimplemented functionality: " << what << std::endl; + std::cerr << Alepha::EndWrap; + abort(); + } + + auto + collectFiles( const std::filesystem::path where, const std::set< std::string > extensions ) + { + if( C::debug ) std::cerr << "Collecting paths within `" << where << "`" << std::endl; + std::vector< std::filesystem::path > rv; + for( const auto entry: std::filesystem::recursive_directory_iterator( where ) ) + { + if( C::debug ) std::cerr << "Examining file: `" << entry << "`" << std::endl; + if( not entry.is_regular_file() ) continue; + if( C::debug ) std::cerr << "Was regular file..." << std::endl; + if( C::debug ) std::cerr << "Examining extension: `" << entry.path().extension() << "`" << std::endl; + if( not extensions.contains( entry.path().extension().string().substr( 1 ) ) ) continue; + if( C::debug ) std::cerr << "Adding collected file: `" << entry << "`" << std::endl; + rv.push_back( entry.path() ); + } + + return rv; + } + + auto + collectSources( const std::filesystem::path where ) + { + // Right now, I only support C++. + // I could expand... later + return collectFiles( where, { "C", "cc", "cpp", "cxx" } ); + } + + auto + collectObjects( const std::filesystem::path where ) + { + return collectFiles( where, { "o" } ); + } + + std::string + compiler() + { + if( getenv( "CXX" ) ) return getenv( "CXX" ); + return "c++"; + } + + std::string + getOptions( const std::string optionName ) + { + if( getenv( optionName.c_str() ) ) return getenv( optionName.c_str() ); + return ""; + } + + auto preprocOptions() { return getOptions( "CPPFLAGS" ); } + + auto compileOptions() { return getOptions( "CXXFLAGS" ); } + + auto linkOptions() { return getOptions( "LDFLAGS" ); } + + struct LinkEntry + { + std::int64_t addr; + std::string type; + std::string symbol; + + friend std::istream & + operator >> ( std::istream &is, LinkEntry &entry ) + { + return is >> entry.addr >> entry.type >> entry.symbol; + } + }; + + namespace C + { + const std::string Defined= "-U"; + const std::string Undefined= "-u"; + } + + std::vector< std::string > + findSymbols( const std::filesystem::path object, const std::string opt ) + { + Alepha::AutoRAII inF + { + [&]{ return popen( ( "nm " + opt + " " + object.string() ).c_str(), "r" ); }, + pclose + }; + std::ifstream in{ "/dev/null" }; + const int handle= in.native_handle(); + ::dup2( fileno( inF ), handle ); + + std::vector< std::string > rv; + std::transform( std::istream_iterator< LinkEntry >{ in }, std::istream_iterator< LinkEntry >{}, + back_inserter( rv ), []( const auto &element ) { return element.symbol; } ); + + return rv; + } + + std::vector< std::filesystem::path > + findMains( const std::vector< std::filesystem::path > objects ) + { + std::vector< std::filesystem::path > rv; + + for( const auto object: objects ) + { + const auto symbols= findSymbols( object, C::Defined ); + + if( std::ranges::contains( symbols, "main" ) ) + { + rv.push_back( object ); + } + } + + return rv; + } + + std::vector< std::string > + findUndefs( std::filesystem::path object ) + { + return findSymbols( object, C::Undefined ); + } + + std::vector< std::string > + findDefs( std::filesystem::path object ) + { + return findSymbols( object, C::Defined ); + } + + auto + computeProviders( const std::vector< std::filesystem::path > objects ) + { + std::multimap< std::string, std::filesystem::path > rv; + + for( const auto object: objects ) + { + for( const auto &symbol: findDefs( object ) ) + { + rv.insert( { symbol, object } ); + } + } + + return rv; + } + + void + linkProgram( const std::filesystem::path &program, const auto &objects, const auto &providers ) + { + std::set< std::filesystem::path > components; + auto resolved= findDefs( program ) | std::ranges::to< std::set >(); + components.insert( program ); + + auto unresolved= findUndefs( program ) | std::ranges::to< std::set >(); + + while( not unresolved.empty() ) + { + auto sym= *unresolved.begin(); + if( not providers.contains( sym ) ) + { + throw std::runtime_error{ "Unable to find a provider for symbol `" + sym + "` when linking `" + + program.stem().string() }; + } + + const auto provider= providers.lower_bound( sym )->second; + const auto provided= findDefs( provider ); + const auto needed= findUndefs( provider ); + + components.insert( provider ); + for( const auto newSym: provided ) + { + resolved.insert( newSym ); + unresolved.erase( newSym ); + } + + for( const auto needSym: needed ) + { + if( not resolved.contains( needSym ) ) + { + unresolved.insert( needSym ); + } + } + } + + std::string command= compiler() + " -o " + program.stem().stem().string(); + for( const auto &component: components ) + { + command+= " " + component.string(); + } + + std::cerr << command << std::endl; + ::system( command.c_str() ); + } + + + void + relink() + { + const auto baseDir= ".bake"; + const auto objects= collectObjects( baseDir ); + + const auto providers= computeProviders( objects ); + + const auto programs= findMains( objects ); + + + for( const auto &program: programs ) linkProgram( program, objects, providers ); + } + + + bool + diff( const std::filesystem::path aPath, const std::filesystem::path bPath ) + { + std::ifstream a{ aPath }; + std::ifstream b{ bPath }; + + return not std::ranges::equal( std::views::istream< char >( a ), std::views::istream< char >( b ) ); + } + + auto + checkRebuilds() + { + const auto allSources= collectSources( "./" ); + std::vector< std::filesystem::path > rv; + + if( C::debug ) std::cerr << "Found " << allSources.size() << " files to process" << std::endl; + + for( const auto &source: allSources ) + { + if( C::debug ) std::cerr << "Processing source file `" << source << "`" << std::endl; + const auto targetdir= ".bake"/source.parent_path(); + std::filesystem::create_directories( ".bake"/source.parent_path() ); + const auto targetBase= ".bake"/source; + + if( not std::filesystem::exists( targetBase.string()+".E" ) ) + { + rv.push_back( source ); + continue; + } + const auto cmdline= compiler() + " " + preprocOptions() + " -E -o " + targetBase.string()+".E.new"; + std::cerr << cmdline << std::endl; + ::system( cmdline.c_str() ); + if( diff( targetBase.string()+".E", targetBase.string()+".E.new" ) ) rv.push_back( source ); + } + + return rv; + } + + void + rebuild( const std::vector< std::filesystem::path > toRebuild ) + { + for( const auto source: toRebuild ) + { + const auto targetdir= ".bake"/source.parent_path(); + std::filesystem::create_directories( ".bake"/source.parent_path() ); + const auto targetBase= ".bake"/source; + + const auto cmdline=compiler() + " " + compileOptions() + " -c -o " + targetBase.string()+".o " + source.string(); + std::cerr << cmdline << std::endl; + system( cmdline.c_str() ); + } + } + + void + run( const std::vector< std::string > &args ) + { + std::vector< std::string > targets; + for( const auto &arg: args ) + { + const auto pos= arg.find_first_of( '=' ); + if( pos == std::string::npos ) + { + targets.push_back( arg ); + } + else + { + ::setenv( arg.substr( 0, pos ).c_str(), arg.substr( pos + 1 ).c_str(), true ); + } + } + + if( not targets.empty() ) unimpl( "Targets not supported at this time" ); + + const auto toRebuild= checkRebuilds(); + + rebuild( toRebuild ); + + relink(); + } +} + +#endif + +int +main( const int argcnt, const char *const *const argvec ) +try +{ + std::filesystem::create_directory( ".bake" ); + run( Alepha::handleOptions( argcnt, argvec ) ); + return EXIT_SUCCESS; +} +catch( const std::exception &ex ) +{ + std::cerr << Alepha::StartWrap{ Alepha::getConsoleWidth() }; + std::cerr << "Error: " << ex.what() << std::endl; + std::cerr << Alepha::EndWrap; + return EXIT_FAILURE; +}