351 lines
8.5 KiB
C++
351 lines
8.5 KiB
C++
static_assert( __cplusplus > 2023'00 );
|
|
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <iterator>
|
|
#include <fstream>
|
|
#include <ranges>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <set>
|
|
|
|
#include <Alepha/Console.h>
|
|
#include <Alepha/ProgramOptions.h>
|
|
#include <Alepha/word_wrap.h>
|
|
|
|
#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;
|
|
}
|