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(); } std::string getExtension( const auto &entry ) { if( entry.path().extension().string().empty() ) return ""; return entry.path().extension().string().substr( 1 ); } 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: `" << getExtension( entry ) << "`" << std::endl; if( not extensions.contains( getExtension( entry ) ) ) 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::string addr; std::string type; std::string 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 ) { const std::string nmCmd= "nm --just-symbols " + opt + " " + object.string(); std::cerr << nmCmd << std::endl; Alepha::AutoRAII inF { [&]{ return popen( nmCmd.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::istream_iterator< std::string >{ in }, std::istream_iterator< std::string >{} }; 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 >(); std::cerr << "In linking `" << program.stem().stem() << "` we have the following unresolved symbols: " << std::endl; for( const auto sym: unresolved ) { std::cerr << " - `" << sym << "`" << std::endl; } while( not unresolved.empty() ) { auto sym= *unresolved.begin(); if( sym.starts_with( "_ZNS" ) or sym.starts_with( "_ZSt" ) or sym == "_GLOBAL_OFFSET_TABLE_" ) { unresolved.erase( sym ); continue; } 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; std::cerr << "Using `" << provider << "` to satisfy `" << sym << "`" << std::endl; 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; const auto cmdline= compiler() + " " + source.string() + " " + preprocOptions() + " -E -o " + targetBase.string()+".E.new"; std::cerr << cmdline << std::endl; ::system( cmdline.c_str() ); if( not std::filesystem::exists( targetBase.string()+".E" ) ) { rv.push_back( source ); continue; } 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() ); std::filesystem::rename( targetBase.string()+".E.new", targetBase.string()+".E" ); } } 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; }