Skip to content

Commit

Permalink
let LoadPackage collect data (#5772)
Browse files Browse the repository at this point in the history
* let `LoadPackage` collect data

- add a new component `LoadInfo` to the `record` argument of
  `PackageAvailabilityInfo`, with value a record that essentially
  collects the information that is up to now printed via `InfoPackageLoading`
  messages

- describe the format of the data in the description of
  `PackageAvailabilityInfo`

- support a global option `LoadInfo` in `LoadPackage`, which sets the given
  value as the `LoadInfo` component of the `record` argument of
  `PackageAvailabilityInfo`;
  this way, one can analyse programmatically why a `LoadPackage` was not
  sucessful

- add tests

- fix a check whether the prescribed GAP version fits:
  Up to now, the `"equal"` variant of `CompareVersionNumbers` was not
  called in one situation; this is interesting mainly for tests,
  thus it was not really a problem

This feature is currently experimental.
Once it gets documented, the right way to do this will be to
document `PackageAvailabilityInfo` in the Reference Manual.

* do not show the current version in messages

(Otherwise we would have to rewrite the tests,
since these version numbers will change over the time.)

* make HPCGAP happy ...

(I think it is strange that the entries of `GAPInfo.PackagesInfo`
are immutable in HPCGAP but mutable in ordinary GAP.)

* added a small evaluation function to the tests

in order to show how one can use the new feature
  • Loading branch information
ThomasBreuer authored Sep 3, 2024
1 parent e6a9da1 commit 4be218f
Show file tree
Hide file tree
Showing 3 changed files with 355 additions and 45 deletions.
21 changes: 21 additions & 0 deletions lib/package.gd
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,30 @@ DeclareGlobalFunction( "LinearOrderByPartialWeakOrder" );
## <P/>
## The arguments <A>record</A> and <A>suggested</A> are used
## for loading packages, as follows.
## <P/>
## The record <A>record</A> collects information about the needed and
## suggested packages of the package <A>name</A>, which allows one to
## compute an appropriate loading order of these packages.
## After the call, the value of the component <C>LoadInfo</C> of
## <A>record</A> is a record with the components
## <List>
## <Item>
## <C>name</C> (the name of the package),
## </Item>
## <Item>
## <C>comment</C> (a string that is empty or describes why the package
## cannot be loaded, independent of the installed version), and
## </Item>
## <Item>
## <C>versions</C> (a list of records, one for each installed version of
## the package that has been checked; each such record has the components
## <C>version</C>, <C>comment</C>, and <C>dependencies</C>,
## where the latter is a list of records for each needed package that was
## checked, and each entry has the same format as the <C>LoadInfo</C>
## record itself.
## </Item>
## </List>
## <P/>
## Finally, the value of <A>suggested</A> determines whether needed and
## suggested packages are considered (value <K>true</K>) or only needed
## packages (value <K>false</K>);
Expand Down
172 changes: 129 additions & 43 deletions lib/package.gi
Original file line number Diff line number Diff line change
Expand Up @@ -663,12 +663,32 @@ InstallGlobalFunction( DisplayPackageLoadingLog, function( arg )
##
InstallGlobalFunction( PackageAvailabilityInfo,
function( name, version, record, suggested, checkall )
local InvalidStrongDependencies, Name, equal, comp, pair, currversion,
inforec, skip, msg, dep, record_local, wght, pos, needed, test,
name2, testpair;
local comp, loadinfo, InvalidStrongDependencies, Name, equal, pair,
currversion, inforec, skip, msg, dep, currloadinfo, GAPequal,
record_local, wght, pos, needed, test, name2, testpair;

# Initialize the dependency info.
for comp in [ "AlreadyHandled", "Dependencies", "StrongDependencies",
"InstallationPaths", "Weights" ] do
if not IsBound( record.( comp ) ) then
record.( comp ):= [];
fi;
od;
if not IsBound( record.LoadInfo ) then
record.LoadInfo:= rec();
fi;

loadinfo:= record.LoadInfo;
if loadinfo = fail then
# happens if one fetches the global option but it is not set
loadinfo:= rec();
fi;
loadinfo.name:= name;
loadinfo.versions:= [];
loadinfo.comment:= "";

InvalidStrongDependencies:= function( dependencies, weights,
strong_dependencies )
strong_dependencies, loadinfo )
local result, order, pair, cycle;

result:= false;
Expand All @@ -686,6 +706,11 @@ InstallGlobalFunction( PackageAvailabilityInfo,
"' but must be" ),
"in the same load cycle, due to other dependencies" ],
Name );
Append( loadinfo.comment,
Concatenation( "package '", pair[1],
"' shall be loaded before package '", name,
"' but must be in the same load cycle, ",
"due to other dependencies, " ) );
result:= true;
if not checkall then
return result;
Expand All @@ -700,63 +725,78 @@ InstallGlobalFunction( PackageAvailabilityInfo,
Name:= name;
name:= LowercaseString( name );
equal:= "";
if 0 < Length( version ) and version[1] = '=' then
if StartsWith( version, "=" ) then
equal:= "equal";
fi;

if name = "gap" then
# This case occurs if a package requires a particular GAP version.
return CompareVersionNumbers( GAPInfo.Version, version, equal );
if CompareVersionNumbers( GAPInfo.Version, version, equal ) then
return true;
else
Append( loadinfo.comment,
Concatenation( "required GAP version ", version,
" is not compatible with the actual version" ) );
return false;
fi;
fi;

# 1. If the package `name' is already loaded then compare the version
# number of the loaded package with the required one.
# (Note that at most one version of a package can be available.)
# If the package `name' is already loaded then compare the version
# number of the loaded package with the required one.
# (Note that at most one version of a package can be available.)
if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
return CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
version, equal );
if CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
version, equal ) then
return true;
else
Append( loadinfo.comment,
Concatenation( "package '", name,
"' is already loaded, required version ", version,
" is not compatible with the actual version" ) );
return false;
fi;
fi;

# 2. If the function was called from `AutoloadPackages'
# and if the package is listed among the packages to be excluded
# from autoload then exit.
# If the function was called from `AutoloadPackages'
# and if the package is listed among the packages to be excluded
# from autoload then exit.
if IsBound( GAPInfo.ExcludeFromAutoload )
and name in GAPInfo.ExcludeFromAutoload then
LogPackageLoadingMessage( PACKAGE_DEBUG,
"package to be excluded from autoload, return 'false'", Name );
Append( loadinfo.comment,
Concatenation( "package '", name,
"' shall be excluded from autoload, return 'false'" ) );
return false;
fi;

# 3. Initialize the dependency info.
for comp in [ "AlreadyHandled", "Dependencies", "StrongDependencies",
"InstallationPaths", "Weights" ] do
if not IsBound( record.( comp ) ) then
record.( comp ):= [];
fi;
od;

# 4. Deal with the case that `name' is among the packages
# from whose tests the current check for `name' arose.
# Deal with the case that `name' is among the packages
# from whose tests the current check for `name' arose.
for pair in record.AlreadyHandled do
if name = pair[1] then
if CompareVersionNumbers( pair[2], version, equal ) then
# The availability of the package will be decided on an outer level.
return fail;
else
# The version assumed on an outer level does not fit.
Append( loadinfo.comment,
Concatenation( "for package '", name,
"', version ", pair[2],
" is assumed on an outer level, ",
"but version ", version, " is required here" ) );
return false;
fi;
fi;
od;

# 5. In recursive calls, regard the current package as handled,
# of course in the version in question.
# In recursive calls, regard the current package as handled,
# of course in the version in question.
currversion:= [ name ];
Add( record.AlreadyHandled, currversion );

# 6. Get the info records for the package `name',
# and take the first record that satisfies the conditions.
# (Note that they are ordered w.r.t. descending version numbers.)
# Get the info records for the package `name',
# and take the first record that satisfies the conditions.
# (Note that they are ordered w.r.t. descending version numbers.)
for inforec in PackageInfo( name ) do

Name:= inforec.PackageName;
Expand All @@ -769,6 +809,11 @@ InstallGlobalFunction( PackageAvailabilityInfo,
dep:= rec();
fi;

currloadinfo:= rec( version:= inforec.Version,
comment:= "",
dependencies:= [] );
Add( loadinfo.versions, currloadinfo );

# Test whether this package version fits.
msg:= [ Concatenation( "PackageAvailabilityInfo for version ",
inforec.Version ) ];
Expand All @@ -782,6 +827,8 @@ InstallGlobalFunction( PackageAvailabilityInfo,
inforec.Version, " does not fit" ),
Concatenation( "(required: ", version, ")" ) ],
inforec.PackageName );
Append( currloadinfo.comment,
Concatenation( "version ", version, "is required, " ) );
if not checkall then
continue;
fi;
Expand All @@ -797,16 +844,23 @@ InstallGlobalFunction( PackageAvailabilityInfo,
fi;

# Test whether the required GAP version fits.
if IsBound( dep.GAP )
and not CompareVersionNumbers( GAPInfo.Version, dep.GAP ) then
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( "PackageAvailabilityInfo: required GAP version (",
dep.GAP, ") does not fit",
inforec.PackageName ) );
if not checkall then
continue;
if IsBound( dep.GAP ) then
GAPequal:= "";
if StartsWith( dep.GAP, "=" ) then
GAPequal:= "equal";
fi;
if not CompareVersionNumbers( GAPInfo.Version, dep.GAP, GAPequal ) then
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( "PackageAvailabilityInfo: required GAP version (",
dep.GAP, ") does not fit",
inforec.PackageName ) );
Append( currloadinfo.comment,
Concatenation( "GAP version ", dep.GAP, " is required, " ) );
if not checkall then
continue;
fi;
skip:= true;
fi;
skip:= true;
fi;

# Test whether the availability test function fits.
Expand All @@ -823,6 +877,9 @@ InstallGlobalFunction( PackageAvailabilityInfo,
Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
" function returned ", String( test ) ),
inforec.PackageName );
Append( currloadinfo.comment,
Concatenation( "the AvailabilityTest function returned ",
String( test ), ", " ) );
if not checkall then
continue;
fi;
Expand All @@ -837,6 +894,9 @@ InstallGlobalFunction( PackageAvailabilityInfo,
inforec.InstallationPath,
"/init.g', please check the installation" ),
inforec.PackageName );
Append( currloadinfo.comment,
Concatenation( "cannot locate '",
inforec.InstallationPath, "/init.g', " ) );
if not checkall then
continue;
fi;
Expand Down Expand Up @@ -892,8 +952,14 @@ InstallGlobalFunction( PackageAvailabilityInfo,
inforec.PackageName );
for pair in needed do
name2:= LowercaseString( pair[1] );
Add( currloadinfo.dependencies,
rec( name:= name2,
versions:= [],
comment:= "" ) );
record_local.LoadInfo:= Last( currloadinfo.dependencies );
testpair:= PackageAvailabilityInfo( name2, pair[2], record_local,
suggested, checkall );

if testpair = false then
# This dependency is not satisfied.
test:= false;
Expand Down Expand Up @@ -922,7 +988,7 @@ InstallGlobalFunction( PackageAvailabilityInfo,
fi;

if InvalidStrongDependencies( record_local.Dependencies,
record_local.Weights, record_local.StrongDependencies ) then
record_local.Weights, record_local.StrongDependencies, currloadinfo ) then
# This package version cannot be loaded due to conditions
# imposed by `OtherPackagesLoadedInAdvance' components.
# (Log messages are added inside the function.)
Expand Down Expand Up @@ -950,7 +1016,8 @@ InstallGlobalFunction( PackageAvailabilityInfo,
record.Weights:= record_local.Weights;

if suggested and IsBound( dep.SuggestedOtherPackages ) then
# Collect info about suggested packages and their dependencies.
# Collect info about suggested packages and their dependencies,
# but without extending 'LoadInfo'.
LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
[ "PackageAvailabilityInfo: check suggested packages" ],
List( dep.SuggestedOtherPackages,
Expand All @@ -961,14 +1028,16 @@ InstallGlobalFunction( PackageAvailabilityInfo,
# Do not change the information collected up to now
# until we are sure that we will really use the suggested package.
record_local:= StructuralCopy( record );
Unbind( record_local.LoadInfo );
test:= PackageAvailabilityInfo( name2, pair[2], record_local,
suggested, checkall );
if test <> true then
Add( record_local.Dependencies, [ name2, name ] );
if IsString( test ) then
if InvalidStrongDependencies( record_local.Dependencies,
record_local.Weights,
record_local.StrongDependencies ) then
record_local.StrongDependencies,
rec( comment:= "" ) ) then
test:= false;
fi;
fi;
Expand Down Expand Up @@ -1532,10 +1601,16 @@ BindGlobal( "GetPackageNameForPrefix", function( prefix )
##
#F LoadPackage( <name>[, <version>][, <banner>] )
##
## The global option <C>LoadInfo</C> (with value a mutable record)
## can be used to collect information about the dependencies
## which have been checked during a call of <Ref Func="LoadPackage"/>.
## After the call, the record will contain data as described for
## <C>PackageAvailabilityInfo</C>.
##
InstallGlobalFunction( LoadPackage, function( arg )
local name, Name, version, banner, loadsuggested, msg, depinfo, path,
pair, i, order, paths, cycle, secondrun, pkgname, pos, info,
filename, entry, r;
filename, entry, r, loadinfo;

# Get the arguments.
if Length( arg ) = 0 then
Expand All @@ -1551,13 +1626,24 @@ InstallGlobalFunction( LoadPackage, function( arg )
name:= GetPackageNameForPrefix( name );
fi;

loadinfo:= ValueOption( "LoadInfo" );
if loadinfo <> fail then
if not ( IsRecord( loadinfo ) and IsMutable( loadinfo ) ) then
Error( "option 'LoadInfo', if given, must be a mutable record" );
fi;
fi;

# Return 'fail' if this package is not installed.
if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
LogPackageLoadingMessage( PACKAGE_DEBUG,
"no package with this name is installed, return 'fail'", name );
if InfoLevel(InfoPackageLoading) < 4 then
Info(InfoWarning,1, name, " package is not available. Check that the name is correct");
Info(InfoWarning,1, "and it is present in one of the GAP root directories (see '??RootPaths')");
if loadinfo <> fail then
loadinfo.name:= name;
loadinfo.comment:= "package is not listed in GAPInfo.PackagesInfo";
fi;
fi;
return fail;
fi;
Expand Down Expand Up @@ -1607,7 +1693,7 @@ InstallGlobalFunction( LoadPackage, function( arg )

# Test whether the package is available,
# and compute the dependency information.
depinfo:= rec();
depinfo:= rec( LoadInfo:= loadinfo );
path:= PackageAvailabilityInfo( name, version, depinfo, loadsuggested,
false );
if not IsString( path ) then
Expand Down
Loading

0 comments on commit 4be218f

Please sign in to comment.