polserver/pol-core/plib/pkg.cpp

523 lines
14 KiB
C++
Raw Permalink Normal View History

/** @file
*
* @par History
*/
2015-01-19 18:18:21 +01:00
#include "pkg.h"
#include "pol_global_config.h"
#include <algorithm>
#include <filesystem>
#include <stdlib.h>
#include <system_error>
2015-01-19 18:18:21 +01:00
#include "../clib/cfgelem.h"
#include "../clib/cfgfile.h"
#include "../clib/clib.h"
2015-01-19 18:18:21 +01:00
#include "../clib/fileutil.h"
#include "../clib/logfacility.h"
#include "../clib/passert.h"
#include "../clib/stlutil.h"
#include "../clib/strutil.h"
#include "systemstate.h"
2015-01-19 18:18:21 +01:00
namespace fs = std::filesystem;
namespace Pol
{
namespace Plib
{
bool Package::provides_system_home_page() const
{
return provides_system_home_page_;
}
Package* find_package( const std::string& pkgname )
{
auto itr = systemstate.packages_byname.find( pkgname );
if ( itr != systemstate.packages_byname.end() )
{
return ( *itr ).second;
}
else
{
return nullptr;
}
}
void remove_package( Package* pkg )
{
auto last = std::remove( systemstate.packages.begin(), systemstate.packages.end(), pkg );
systemstate.packages.erase( last, systemstate.packages.end() );
auto itr = systemstate.packages_byname.begin();
while ( itr != systemstate.packages_byname.end() )
{
if ( itr->second == pkg )
{
systemstate.packages_byname.erase( itr );
break;
}
++itr;
}
}
void compare_versions( const std::string& verleft, const std::string& verright, bool& isgreater,
bool& isequal )
{
isgreater = isequal = false;
const char* vneed = verright.c_str();
const char* vhave = verleft.c_str();
while ( ( vneed != nullptr && vneed[0] != '\0' ) || ( vhave != nullptr && vhave[0] != '\0' ) )
{
char* new_vneed = nullptr;
char* new_vhave = nullptr;
unsigned int vneedpart, vhavepart;
vneedpart = ( vneed != nullptr ) ? strtoul( vneed, &new_vneed, 0 ) : 0;
vhavepart = ( vhave != nullptr ) ? strtoul( vhave, &new_vhave, 0 ) : 0;
if ( vhavepart > vneedpart )
{
isgreater = true;
return;
}
else if ( vhavepart < vneedpart )
{
return;
}
else // same, so check the next one
{
vneed = new_vneed;
if ( vneed && *vneed )
++vneed;
vhave = new_vhave;
if ( vhave && *vhave )
++vhave;
}
}
isequal = true;
}
bool version_greater_or_equal( const std::string& version_have, const std::string& version_need )
{
bool isgreater;
bool isequal;
compare_versions( version_have, version_need, isgreater, isequal );
return ( isequal || isgreater );
}
bool version_equal( const std::string& version_have, const std::string& version_need )
{
bool isgreater;
bool isequal;
compare_versions( version_have, version_need, isgreater, isequal );
return isequal;
}
void test_check_version()
{ // have // need
passert( version_greater_or_equal( "0", "0" ) == true );
passert( version_greater_or_equal( "1", "0" ) == true );
passert( version_greater_or_equal( "0", "1" ) == false );
passert( version_greater_or_equal( "0.5", "1" ) == false );
passert( version_greater_or_equal( "0.5", "0" ) == true );
passert( version_greater_or_equal( "1.2", "1.12" ) == false );
passert( version_greater_or_equal( "1.12", "1.2" ) == true );
passert( version_greater_or_equal( "1.2.3", "1" ) == true );
passert( version_greater_or_equal( "1.1", "1.2.3" ) == false );
passert( version_greater_or_equal( "1.3", "1.2.3" ) == true );
passert( version_equal( "93", "93.0.0" ) == true );
passert( version_equal( "93.0.0", "93" ) == true );
}
PackageList::PackageList( Clib::ConfigElem& elem, const char* tag )
{
std::string tmp;
while ( elem.remove_prop( tag, &tmp ) )
{
Clib::mklowerASCII( tmp );
ISTRINGSTREAM is( tmp );
Elem _elem;
if ( is >> _elem.pkgname )
{
if ( !( is >> _elem.version ) )
_elem.version = "0";
elems.push_back( _elem );
}
}
}
size_t PackageList::sizeEstimate() const
{
size_t size = sizeof( PackageList );
for ( const auto& elem : elems )
size += elem.pkgname.capacity() + elem.version.capacity();
return size;
}
Package::Package( const std::string& pkg_dir, Clib::ConfigElem& elem )
Introduce DAP Debugger (#608) * Add cppdap debugger library and simple server (#508) * Add cppdap library * Add cppdap server * fixup cmake scripts for windows * Enable caching of cppdap on GitHub actions * Fix CMake for cppdap target * Wait 1s for session close before thread ends * Address review comments - Remove unnecessary use of wait_for - Fix wait_for method * Do not use SocketClientThread for cppdap clients * Address review comments - Cleanup object ownership * Add authorization and event handling (#510) * Add authorization and event handling [WIP] * Use std::weak_ptr; Only allow one debugger conn * Only attach debugger if dbg file exists * Cleanup weak_ptr; Fix listeners; Add handler stubs * Address review comments - PolLock on uoexec and weakptr access - Use make_shared when creating client thread * Add ExecutorDebugListener on_destroy callback * Use cppdap sockets instead of clib sockets * Update cppdap repository to 2703cf4 * Implement server address binding * Update cppdap repository to eeca5ce * Fix warnings * Use weak_from_this * Fix stalled executors on disconnect * Fix stalled executors on re-attach * Clear uoexec weakptr on all debugger detaches * Move server to gamestate * move DapDebugServer into Network namespace * move DapDebugServer into GameState for storage * Move DapDebugServer into NetworkManager * Fix missing memory include * Add more events (#513) * Add more events * Implement dbg_step_out * Fix breakpoint clearing * Add even more events (#515) * add launch request * Add custom `processes` command * fixup loop * Refactoring to move handlers to member functions * Add VariablesRequest and ScopesRequest * use std::varaint for Handles * add pollocks * refactor Handles to new file * move custom messages to proto.h * move clientthread * some formatting / cleanup * Add missing PolLock * move dap variables creation to Handles * appobj members * rename ClientThread to DebugClientThread * rename LaunchRequest args to arg * Fix compiler error on size_t * Add support for EvaluateRequest and SetVariableRequest (#519) * Update grammar for evaluateUnit * Cleanup various compiler code * Add support for EvaluateRequest Only supports values, variables, element access, member access * Add report clearing to reinitialize state * Add support for SetVariableRequest * Fix CI build errors * Move ExpressionEvaluator to Bscript * Fix formatting * Fix include * Refactor global and frame variable references * Encapsulate Executor debugging environment; Fix step over (#524) * Fix step over * Some cleanup of executor - Remove unused debug states - Rename members for consistency * move debugging environment to new class * Move instruction inspection to class * Cleanup slashes on Windows; Add Executor state to processes response (#527) * Fix backspaces in script names on Windows * Fix compilation warnings with data loss * Use boost::icontains for string comparison * Add Executor state to processes response * Updates for libfmt * Fix tests; Fix cppdap build for Mac universal binary (#607) * fix tests * fix cppdap build for mac universal binary * Update cppdap to 2a4c7cf * Add tests; Modify launch behavior to not stop at entry (redo) (#609) * Remove unused setExceptionBreakpoints handler * Add debugger tests * Launching a script should not set attaching state * Add launch tests * Do not check for connection in debugger tests * Rename `program` to `script` in protocol structs * Fix launch stopAtEntry; quote string var values * Fix compilation error on Windows * Add configfiles doc entry * Various fixes for finishing touches; Add core-changes (#610) * switch cmake projects * simple docs * cmake move make_directory * Fix launch request with relative, absolute, package paths * make relative paths as absolute in source request * Add true, false, uninit to ExpressionEvaluator * Re-add setExceptionBreakpoints to avoid unhandled message errors * Better debug thread logging with instance numbers * launched scripts should terminate on disconnect (according to DAP spec) * Properly handle exited event in client thread * Add core-changes * Address Discord comments - Mention vscode-escript in core-changes - Re-add "cppdap already built" in cmake * change cmake escriptgrammarlib visibility to public
2024-01-28 15:49:55 +07:00
: dir_( Clib::normalized_dir_form( pkg_dir ) ),
name_( elem.remove_string( "Name" ) ),
version_( elem.remove_string( "Version", "0" ) ),
core_required_( "" ),
requires_( elem, "Requires" ),
conflicts_( elem, "Conflicts" ),
replaces_( elem, "Replaces" ),
provides_system_home_page_( elem.remove_bool( "ProvidesSystemHomePage", false ) )
{
Clib::mklowerASCII( name_ );
std::string tmp = elem.read_string( "CoreRequired", "0" );
if ( isdigit( tmp[0] ) )
{
core_required_ = elem.remove_string( "CoreRequired", "" );
}
else
{
// Forms like CoreRequired POL095-2003-01-28 are no longer allowed.
ERROR_PRINTLN(
"Error in package {}({}):\n"
" Core version must be a semantic version (n.n.n), but got "
"\"{}\".",
name_, dir_, core_required_ );
throw std::runtime_error( "Malformed CoreRequired package field" );
}
}
std::string Package::desc() const
{
return name() + " (" + dir() + ")";
}
bool Package::check_replacements() const
{
bool any = false;
for ( const auto& elem : replaces_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found != nullptr )
2015-01-19 18:18:21 +01:00
{
any = true;
INFO_PRINTLN( "Package {} replaces package {}", desc(), found->desc() );
remove_package( found );
delete found;
found = nullptr;
2015-01-19 18:18:21 +01:00
}
}
return any;
}
2015-01-19 18:18:21 +01:00
void Package::check_dependencies() const
{
if ( !core_required_.empty() )
{
if ( !( version_greater_or_equal( POL_VERSION_STR, core_required_ ) ) )
{
ERROR_PRINTLN(
"Error in package {}:\n"
" Core version {} is required, but version {} is running.",
desc(), core_required_, POL_VERSION_STR );
throw std::runtime_error( "Package requires a newer core version" );
}
}
for ( const auto& elem : requires_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found == nullptr )
{
ERROR_PRINTLN(
"Error in package '{}' ({}):\n"
" Package '{}' is required, but is not installed.",
name_, dir_, elem.pkgname );
throw std::runtime_error( "Package dependency error" );
}
else
2015-01-19 18:18:21 +01:00
{
if ( !version_greater_or_equal( found->version_, elem.version ) )
{
ERROR_PRINTLN(
"Error in package '{}' ({}):\n"
" Package '{}' version {} is required, but version {} "
"was found",
name_, dir_, elem.pkgname, elem.version, found->version_ );
throw std::runtime_error( "Package dependency error" );
}
2015-01-19 18:18:21 +01:00
}
}
}
2015-01-19 18:18:21 +01:00
void Package::check_conflicts() const
{
for ( const auto& elem : conflicts_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found != nullptr )
{
ERROR_PRINTLN(
"Error in package {}:\n"
" Package conflicts with package {}",
desc(), found->desc() );
throw std::runtime_error( "Package dependency error" );
}
2015-01-19 18:18:21 +01:00
}
}
size_t Package::estimateSize() const
{
size_t size = dir_.capacity() + name_.capacity() + version_.capacity() +
+core_required_.capacity() + requires_.sizeEstimate() + conflicts_.sizeEstimate() +
replaces_.sizeEstimate() + sizeof( bool ) /*provides_system_home_page_*/
;
return size;
}
void load_package( const std::string& pkg_dir, Clib::ConfigElem& elem, bool quiet )
{
std::unique_ptr<Package> pkg( new Package( pkg_dir, elem ) );
Package* existing_pkg = find_package( pkg->name() );
if ( existing_pkg != nullptr )
{
bool isgreater, isequal;
compare_versions( pkg->version(), existing_pkg->version(), isgreater, isequal );
if ( isgreater )
{
// replace existing package with newer version
if ( !quiet )
INFO_PRINTLN( "Replacing package {} version {} with version {} found in {}",
existing_pkg->desc(), existing_pkg->version(), pkg->version(), pkg->dir() );
remove_package( existing_pkg );
delete existing_pkg;
existing_pkg = nullptr;
}
else if ( isequal )
{
ERROR_PRINTLN(
"Error in package {}:\n"
" Package by same name already found in {}",
pkg->desc(), existing_pkg->desc() );
throw std::runtime_error( "Duplicate package found" );
}
else
{
// skip this package, its version is older
if ( !quiet )
INFO_PRINTLN( "Skipping package {} version {} because version {} was already found in ",
pkg->desc(), pkg->version(), existing_pkg->version(), existing_pkg->dir() );
return;
}
}
systemstate.packages.push_back( pkg.get() );
Package* ppkg = pkg.release();
systemstate.packages_byname.insert( PackagesByName::value_type( ppkg->name(), ppkg ) );
}
void load_packages( const std::string& basedir, bool quiet )
{
std::error_code ec;
for ( const auto& dir_entry : fs::directory_iterator( basedir, ec ) )
{
if ( !dir_entry.is_directory() )
continue;
if ( auto fn = dir_entry.path().filename().u8string();
!fn.empty() && ( *fn.begin() == '.' || fn == "template" ) )
continue;
const auto pkg_dir = dir_entry.path();
const auto pkg_cfg = pkg_dir / "pkg.cfg";
if ( fs::exists( pkg_cfg ) )
{
Clib::ConfigFile cf( pkg_cfg.u8string().c_str() );
Clib::ConfigElem elem;
cf.readraw( elem );
if ( ( elem.remove_bool( "Enabled" ) == true || fs::exists( pkg_dir / "enabled.pkg" ) ) &&
!fs::exists( pkg_dir / "disabled.pkg" ) )
{
if ( !quiet )
INFO_PRINTLN( "Loading package in {}", pkg_dir.u8string() );
load_package( pkg_dir.u8string() + "/", elem, quiet );
load_packages( pkg_dir.u8string(), quiet );
}
}
else
{
load_packages( pkg_dir.u8string(), quiet );
}
}
}
void check_deps_for_package( const Package* pkg )
{
pkg->check_dependencies();
pkg->check_conflicts();
}
void replace_packages()
{
bool done;
do
{
done = true;
for ( const auto& pkg : systemstate.packages )
{
bool change = pkg->check_replacements();
if ( change )
{
done = false;
break;
}
}
} while ( !done );
}
void check_package_deps()
{
for ( const auto& pkg : systemstate.packages )
check_deps_for_package( pkg );
}
void load_packages( bool quiet )
{
test_check_version();
load_packages( "pkg/", quiet );
if ( Clib::FileExists( "config/pkgroots.cfg" ) )
{
Clib::ConfigFile cf( "config/pkgroots.cfg", "PackageRoot" );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
std::string dir;
while ( elem.remove_prop( "dir", &dir ) )
{
dir = Clib::normalized_dir_form( dir );
if ( !quiet )
INFO_PRINTLN( "Searching for packages under {}", dir );
load_packages( dir.c_str(), quiet );
}
}
}
replace_packages();
check_package_deps();
// sort pkg vector by name, so e.g. startup order is in a defined and maybe also expected order.
std::sort( systemstate.packages.begin(), systemstate.packages.end(),
[]( const Package* pkg1, const Package* pkg2 )
{ return pkg1->name() < pkg2->name(); } );
}
bool pkgdef_split( const std::string& spec, const Package* inpkg, const Package** outpkg,
std::string* path )
{
if ( spec[0] == ':' )
{
if ( spec[1] == ':' ) // '::corefile' -- a core file
{
*outpkg = nullptr;
*path = spec.substr( 2, std::string::npos );
}
else // ':pkgname:pkgfile' -- a packaged file
{
std::string::size_type second_colon = spec.find( ':', 2 );
if ( second_colon != std::string::npos )
{
std::string pkgname = spec.substr( 1, second_colon - 1 );
std::string pkgfile = spec.substr( second_colon + 1, std::string::npos );
Package* dstpkg = find_package( pkgname );
if ( dstpkg != nullptr )
{
*outpkg = dstpkg;
*path = pkgfile;
}
else
{
ERROR_PRINTLN( "Unable to find package '{}'", pkgname );
return false;
}
}
else
{
ERROR_PRINTLN( "Poorly formed packagefile descriptor: '{}'", spec );
return false;
}
}
}
else
{
*outpkg = inpkg;
*path = spec;
}
return true;
}
void load_packaged_cfgs( const char* cfgname, const char* taglist,
void ( *loadentry )( const Package*, Clib::ConfigElem& ) )
{
for ( const auto& pkg : systemstate.packages )
{
std::string filename = GetPackageCfgPath( pkg, cfgname );
if ( Clib::FileExists( filename.c_str() ) )
{
Clib::ConfigFile cf( filename.c_str(), taglist );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
loadentry( pkg, elem );
}
}
}
}
void load_all_cfgs( const char* cfgname, const char* taglist,
void ( *loadentry )( const Package*, Clib::ConfigElem& ) )
{
std::string filename = std::string( "config/" ) + cfgname;
if ( Clib::FileExists( filename ) )
{
Clib::ConfigFile cf( filename.c_str(), taglist );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
loadentry( nullptr, elem );
}
}
load_packaged_cfgs( cfgname, taglist, loadentry );
}
2016-12-15 16:59:58 +01:00
std::string GetPackageCfgPath( const Package* pkg, const std::string& filename )
{
std::string filepath;
if ( pkg == nullptr )
{ // If no package is sent, assume pol/config/file.xxx
filepath = "config/" + filename;
}
else
{ // ** Going to save this feature for 097 **
// With packages, first try for /pkg/config/file.xxx
filepath = pkg->dir() + "config/" + filename;
if ( !Clib::FileExists( filepath ) )
{
// Lastly, assume /pkg/file.xxx
filepath = pkg->dir() + filename;
}
}
return filepath;
}
} // namespace Plib
} // namespace Pol