|
#############################################################################
##
## This file is part of GAP, a system for computational discrete algebra.
## This file's authors include Frank Celler, Alexander Hulpke.
##
## Copyright of GAP belongs to its developers, whose names are too numerous
## to list here. Please refer to the COPYRIGHT file for details.
##
## SPDX-License-Identifier: GPL-2.0-or-later
##
## This file contains support for ⪆ packages.
##
# recode string to GAPInfo.TermEncoding, assuming input is UTF-8 or latin1
# (if useful this may become documented for general use)
BindGlobal( "RecodeForCurrentTerminal", function( str )
local fun, u;
if IsBoundGlobal( "Unicode" ) and IsBoundGlobal( "Encode" ) then
# The GAPDoc package is completely loaded.
fun:= ValueGlobal( "Unicode" );
u:= fun( str, "UTF-8" );
if u = fail then
u:= fun( str, "ISO-8859-1");
fi;
if GAPInfo.TermEncoding <> "UTF-8" then
fun:= ValueGlobal( "SimplifiedUnicodeString" );
u:= fun( u, GAPInfo.TermEncoding );
fi;
fun:= ValueGlobal( "Encode" );
u:= fun( u, GAPInfo.TermEncoding );
return u;
else
# GAPDoc is not yet available, do nothing in this case.
return str;
fi;
end );
#############################################################################
##
#F CompareVersionNumbers( <supplied>, <required>[, "equal"] )
##
InstallGlobalFunction( CompareVersionNumbers, function( arg )
local s, r, inequal, i, j, a, b;
s:= arg[1];
r:= arg[2];
inequal:= not ( Length( arg ) = 3 and arg[3] = "equal" );
# Deal with the case of a `dev' version.
if 2 < Length( s )
and s{ [ Length( s ) - 2 .. Length( s ) ] } = "dev" then
return inequal or ( Length(r)>2 and r{[Length(r)-2..Length(r)]}="dev" );
elif 2 < Length( r )
and r{ [ Length( r ) - 2 .. Length( r ) ] } = "dev" then
return false;
fi;
while 0 < Length( s ) or 0 < Length( r ) do
# Remove leading non-digit characters.
i:= 1;
while i <= Length( s ) and not IsDigitChar( s[i] ) do
i:= i+1;
od;
s:= s{ [ i .. Length( s ) ] };
j:= 1;
while j <= Length( r ) and not IsDigitChar( r[j] ) do
j:= j+1;
od;
r:= r{ [ j .. Length( r ) ] };
# If one of the two strings is empty then we are done.
if Length( s ) = 0 then
return Length( r ) = 0;
elif Length( r ) = 0 then
return inequal;
fi;
# Compare the next portion of digit characters.
i:= 1;
while i <= Length( s ) and IsDigitChar( s[i] ) do
i:= i+1;
od;
a:= Int( s{ [ 1 .. i-1 ] } );
j:= 1;
while j <= Length( r ) and IsDigitChar( r[j] ) do
j:= j+1;
od;
b:= Int( r{ [ 1 .. j-1 ] } );
if a < b then
return false;
elif b < a then
return inequal;
fi;
s:= s{ [ i .. Length( s ) ] };
r:= r{ [ j .. Length( r ) ] };
od;
# The two remaining strings are empty.
return true;
end );
#############################################################################
##
#F PackageInfo( <pkgname> )
##
InstallGlobalFunction( PackageInfo, function( pkgname )
pkgname:= LowercaseString( pkgname );
if not IsBound( GAPInfo.PackagesInfo.( pkgname ) ) then
return [];
else
return GAPInfo.PackagesInfo.( pkgname );
fi;
end );
#############################################################################
##
#F RECORDS_FILE( <name> )
##
InstallGlobalFunction( RECORDS_FILE, function( name )
local str, rows, recs, pos, r;
str:= StringFile( name );
if str = fail then
return [];
fi;
rows:= SplitString( str, "", "\n" );
recs:= [];
for r in rows do
# remove comments starting with `#'
pos:= Position( r, '#' );
if pos <> fail then
r:= r{ [ 1 .. pos-1 ] };
fi;
Append( recs, SplitString( r, "", " \n\t\r" ) );
od;
return List( recs, LowercaseString );
end );
#############################################################################
##
#F SetPackageInfo( <record> )
##
InstallGlobalFunction( SetPackageInfo, function( record )
local rnam, info;
if IsHPCGAP then
info := rec();
for rnam in REC_NAMES(record) do
info.(rnam) := Immutable(record.(rnam));
od;
record := info;
fi;
GAPInfo.PackageInfoCurrent:= record;
end );
#############################################################################
##
#F FindPackageInfosInSubdirectories( pkgdir, name )
##
## Finds all PackageInfos in subdirectories of directory name in
## directory pkgdir, return a list of their paths.
##
BindGlobal( "FindPackageInfosInSubdirectories", function( pkgdir, name )
local pkgpath, file, files, subdir;
pkgpath:= Filename( [ pkgdir ], name );
# This can be 'fail' if 'name' is a void link.
if pkgpath = fail then
return [];
fi;
if not IsDirectoryPath( pkgpath ) then
return [];
fi;
if name in [ ".", ".." ] then
return [];
fi;
file:= Filename( [ pkgdir ],
Concatenation( name, "/PackageInfo.g" ) );
if file = fail then
files := [];
# Perhaps some subdirectories contain `PackageInfo.g' files.
for subdir in Set( DirectoryContents( pkgpath ) ) do
if not subdir in [ ".", ".." ] then
pkgpath:= Filename( [ pkgdir ],
Concatenation( name, "/", subdir ) );
if pkgpath <> fail and IsDirectoryPath( pkgpath )
and not subdir in [ ".", ".." ] then
file:= Filename( [ pkgdir ],
Concatenation( name, "/", subdir, "/PackageInfo.g" ) );
if file <> fail then
Add( files,
[ file, Concatenation( name, "/", subdir ) ] );
fi;
fi;
fi;
od;
else
files:= [ [ file, name ] ];
fi;
return files;
end );
#############################################################################
##
#F AddPackageInfo( files )
##
BindGlobal( "AddPackageInfos", function( files, pkgdir, ignore )
local file, record, pkgname, date, dd, mm;
for file in files do
# Read the `PackageInfo.g' file.
Unbind( GAPInfo.PackageInfoCurrent );
Read( file[1] );
if IsBound( GAPInfo.PackageInfoCurrent ) then
record:= GAPInfo.PackageInfoCurrent;
Unbind( GAPInfo.PackageInfoCurrent );
pkgname:= LowercaseString( record.PackageName );
NormalizeWhitespace( pkgname );
# Check whether GAP wants to reset loadability.
if IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
record ) = false then
Add( GAPInfo.PackagesInfoRefuseLoad, record );
elif pkgname in ignore then
LogPackageLoadingMessage( PACKAGE_DEBUG,
Concatenation( "ignore package ", record.PackageName,
" (user preference PackagesToIgnore)" ), "GAP" );
else
record.InstallationPath:= Filename( [ pkgdir ], file[2] );
# normalize to include trailing "/"
record.InstallationPath:= Filename( [ Directory( record.InstallationPath ) ], "" );
if not IsBound( record.PackageDoc ) then
record.PackageDoc:= [];
elif IsRecord( record.PackageDoc ) then
record.PackageDoc:= [ record.PackageDoc ];
fi;
# Normalize the format of 'Date', i.e. if it is the format yyyy-mm-dd
# then we change it to dd/mm/yyyy. When other tools have adapted to
# the yyyy-mm-dd format we can normalize to that format and at some
# point in the future get rid of this code.
if Length(record.Date) = 10 and record.Date{[5,8]} = "--" then
date := List( SplitString( record.Date, "-" ), Int);
date := Permuted(date, (1,3)); # date = [dd,mm,yyyy]
# generate the day and month strings
# if the day has only one digit we have to add a 0
if date[1] < 10 then
dd := Concatenation("0", String(date[1]));
else
dd := String(date[1]);
fi;
# if the month has only one digit we have to add a 0
if date[2] < 10 then
mm := Concatenation("0", String(date[2]));
else
mm := String(date[2]);
fi;
record.Date := Concatenation(dd, "/", mm, "/", String(date[3]));
fi;
if IsHPCGAP then
# FIXME: we make the package info record immutable, to
# allow access from multiple threads; but that in turn
# can break packages, which rely on their package info
# record being readable (see issue #2568)
MakeImmutable(record);
fi;
Add( GAPInfo.PackagesInfo, record );
fi;
fi;
od;
end );
#############################################################################
##
#F InitializePackagesInfoRecords()
##
## In earlier versions, this function had an argument; now we ignore it.
##
InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
local pkgdirs, pkgdir, pkgdirstrs, ignore, name, file, files, record, r;
if IsBound( GAPInfo.PackagesInfoInitialized ) and
GAPInfo.PackagesInfoInitialized = true then
# This function has already been executed in this session.
return;
fi;
GAPInfo.LoadPackageLevel:= 0;
GAPInfo.PackagesInfo:= [];
GAPInfo.PackagesInfoRefuseLoad:= [];
LogPackageLoadingMessage( PACKAGE_DEBUG,
"entering InitializePackagesInfoRecords", "GAP" );
# the first time this is called, add the cmd line args to the list
if IsEmpty(GAPInfo.PackageDirectories) then
for pkgdirstrs in GAPInfo.CommandLineOptions.packagedirs do
pkgdirs:= List( SplitString( pkgdirstrs, ";" ), Directory );
for pkgdir in pkgdirs do
if not pkgdir in GAPInfo.PackageDirectories then
Add( GAPInfo.PackageDirectories, pkgdir );
fi;
od;
od;
fi;
# add any new pkg directories to the list
pkgdirs:= DirectoriesLibrary( "pkg" );
if pkgdirs <> fail then
pkgdirs:= Filtered( pkgdirs, dir -> not dir in GAPInfo.PackageDirectories );
if not IsEmpty(pkgdirs) then
Append( GAPInfo.PackageDirectories, pkgdirs );
fi;
fi;
if IsEmpty(GAPInfo.PackageDirectories) then
LogPackageLoadingMessage( PACKAGE_DEBUG,
"exit InitializePackagesInfoRecords (no pkg directories found)",
"GAP" );
GAPInfo.PackagesInfo:= AtomicRecord();
return;
fi;
if IsBound( GAPInfo.ExcludeFromAutoload ) then
# The function was called from `AutoloadPackages'.
# Collect the `NOAUTO' information in a global list,
# which will be used in `AutoloadPackages' for both packages to be
# loaded according to the user preference "PackagesToLoad"
# and suggested packages of needed packages.
# The component `GAPInfo.ExcludeFromAutoload' will be unbound after the
# call of `AutoloadPackages'.
GAPInfo.ExcludeFromAutoload:= Set(
UserPreference( "ExcludeFromAutoload" ), LowercaseString );
fi;
# Do not store information about packages in "PackagesToIgnore".
ignore:= List( UserPreference( "PackagesToIgnore" ), LowercaseString );
# Loop over the package directories,
# remove the packages listed in `NOAUTO' files from GAP's suggested
# packages, and unite the information for the directories.
for pkgdir in GAPInfo.PackageDirectories do
if IsBound( GAPInfo.ExcludeFromAutoload ) then
UniteSet( GAPInfo.ExcludeFromAutoload,
List( RECORDS_FILE( Filename( pkgdir, "NOAUTO" ) ),
LowercaseString ) );
fi;
# pkgdir may be a package instead of a package directory
file:= Filename( [ pkgdir ], "PackageInfo.g" );
if file <> fail then
AddPackageInfos( [ [ file, "" ] ], pkgdir, ignore );
else
# Loop over subdirectories of this package directory.
for name in Set( DirectoryContents( pkgdir ) ) do
## Get all package dirs
files := FindPackageInfosInSubdirectories( pkgdir, name );
AddPackageInfos( files, pkgdir, ignore );
od;
fi;
od;
# Sort the available info records by their version numbers.
# (Sort stably in order to make sure that an instance from the first
# possible root path gets chosen if the same version of a package
# is available in several root paths.
# Note that 'CompareVersionNumbers' returns 'true'
# if the two arguments are equal.)
StableSortParallel( List( GAPInfo.PackagesInfo, r -> r.Version ),
GAPInfo.PackagesInfo,
{ a, b } -> not CompareVersionNumbers( a, b, "equal" ) and CompareVersionNumbers( a, b ) );
# Turn the lists into records.
record:= rec();
for r in GAPInfo.PackagesInfo do
name:= LowercaseString( r.PackageName );
if IsBound( record.( name ) ) then
record.( name ) := Concatenation( record.( name ), [ r ] );
else
record.( name ):= [ r ];
fi;
if IsHPCGAP then
# FIXME: we make the package info record immutable, to
# allow access from multiple threads; but that in turn
# can break packages, which rely on their package info
# record being readable (see issue #2568)
MakeImmutable( record.( name ) );
fi;
od;
GAPInfo.PackagesInfo:= AtomicRecord(record);
GAPInfo.PackagesInfoInitialized:= true;
LogPackageLoadingMessage( PACKAGE_DEBUG,
"return from InitializePackagesInfoRecords", "GAP" );
end );
#############################################################################
##
#F LinearOrderByPartialWeakOrder( <pairs>, <weights> )
##
## The algorithm works with a directed graph
## whose vertices are subsets of the <M>c_i</M>
## and whose edges represent the given partial order.
## We start with one vertex for each <M>x_i</M> and each <M>y_i</M>
## from the input list, and draw an edge from <M>x_i</M> to <M>y_i</M>.
## Furthermore,
## we need a queue <M>Q</M> of the smallest vertices found up to now,
## and a stack <M>S</M> of the largest vertices found up to now;
## both <M>Q</M> and <M>S</M> are empty at the beginning.
## Now we add the vertices without predecessors to <M>Q</M> and remove the
## edges from these vertices until no more such vertex is found.
## Then we put the vertices without successors on <M>S</M> and remove the
## edges to these vertices until no more such vertex is found.
## If edges are left then each of them leads eventually into a cycle in the
## graph; we find a cycle and amalgamate it into a new vertex.
## Now we repeat the process until all edges have disappeared.
## Finally, the concatenation of <M>Q</M> and <M>S</M> gives us the sets
## <M>c_i</M>.
##
InstallGlobalFunction( LinearOrderByPartialWeakOrder,
function( pairs, weights )
local Q, S, Qw, Sw, F, pair, vx, vy, v, pos, candidates, minwght,
smallest, s, maxwght, largest, p, cycle, next, new;
# Initialize the queue and the stack.
Q:= [];
S:= [];
Qw:= [];
Sw:= [];
# Create a list of vertices according to the input.
F:= [];
for pair in Set( pairs ) do
if pair[1] <> pair[2] then
vx:= First( F, r -> r.keys[1] = pair[1] );
if vx = fail then
vx:= rec( keys:= [ pair[1] ], succ:= [], pred:= [] );
Add( F, vx );
fi;
vy:= First( F, r -> r.keys[1] = pair[2] );
if vy = fail then
vy:= rec( keys:= [ pair[2] ], succ:= [], pred:= [] );
Add( F, vy );
fi;
Add( vx.succ, vy );
Add( vy.pred, vx );
fi;
od;
# Assign the weights.
weights:= SortedList( weights );
for v in F do
pos:= PositionSorted( weights, v.keys );
if pos <= Length( weights ) and weights[ pos ][1] = v.keys[1] then
v.wght:= weights[ pos ][2];
else
v.wght:= 0;
fi;
od;
# While F contains a vertex, ...
while not IsEmpty( F ) do
# ... find the vertices in F without predecessors and add them to Q,
# remove the edges from these vertices,
# and remove these vertices from F.
candidates:= Filtered( F, v -> IsEmpty( v.pred ) );
if not IsEmpty( candidates ) then
minwght:= infinity; # larger than all admissible weights
for v in candidates do
if v.wght < minwght then
minwght:= v.wght;
smallest:= [ v ];
elif v.wght = minwght then
Add( smallest, v );
fi;
od;
for v in smallest do
Add( Q, v.keys );
Add( Qw, v.wght );
for s in v.succ do
s.pred:= Filtered( s.pred, x -> not IsIdenticalObj( v, x ) );
if IsEmpty( s.pred )
and ForAll( smallest, x -> not IsIdenticalObj( s, x ) ) then
Add( smallest, s );
fi;
od;
pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
Unbind( F[ pos ] );
F:= Compacted( F );
od;
fi;
# Then find the vertices in F without successors and put them on S,
# remove the edges to these vertices,
# and remove these vertices from F.
candidates:= Filtered( F, v -> IsEmpty( v.succ ) );
if not IsEmpty( candidates ) then
maxwght:= -1; # smaller than all admissible weights
for v in candidates do
if v.wght > maxwght then
maxwght:= v.wght;
largest:= [ v ];
elif v.wght = maxwght then
Add( largest, v );
fi;
od;
for v in largest do
Add( S, v.keys );
Add( Sw, v.wght );
for p in v.pred do
p.succ:= Filtered( p.succ, x -> not IsIdenticalObj( v, x ) );
if IsEmpty( p.succ )
and ForAll( largest, x -> not IsIdenticalObj( p, x ) ) then
Add( largest, p );
fi;
od;
pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
Unbind( F[ pos ] );
F:= Compacted( F );
od;
fi;
if not IsEmpty( F ) then
# Find a cycle in F.
# (Note that now any vertex has a successor,
# so we may start anywhere, and eventually get into a cycle.)
cycle:= [];
next:= F[1];
repeat
Add( cycle, next );
next:= next.succ[1];
pos:= PositionProperty( cycle, x -> IsIdenticalObj( x, next ) );
until pos <> fail;
cycle:= cycle{ [ pos .. Length( cycle ) ] };
# Replace the set of vertices in the cycle by a new vertex,
# replace all edges from/to a vertex outside the cycle
# to/from a vertex in the cycle by edges to/from the new vertex.
new:= rec( keys:= [], succ:= [], pred:= [],
wght:= Maximum( List( cycle, v -> v.wght ) ) );
for v in cycle do
UniteSet( new.keys, v.keys );
for s in v.succ do
if ForAll( cycle, w -> not IsIdenticalObj( s, w ) ) then
if ForAll( new.succ, w -> not IsIdenticalObj( s, w ) ) then
Add( new.succ, s );
fi;
pos:= PositionProperty( s.pred, w -> IsIdenticalObj( v, w ) );
if ForAll( s.pred, w -> not IsIdenticalObj( new, w ) ) then
s.pred[ pos ]:= new;
else
Unbind( s.pred[ pos ] );
s.pred:= Compacted( s.pred );
fi;
fi;
od;
for p in v.pred do
if ForAll( cycle, w -> not IsIdenticalObj( p, w ) ) then
if ForAll( new.pred, w -> not IsIdenticalObj( p, w ) ) then
Add( new.pred, p );
fi;
pos:= PositionProperty( p.succ, w -> IsIdenticalObj( v, w ) );
if ForAll( p.succ, w -> not IsIdenticalObj( new, w ) ) then
p.succ[ pos ]:= new;
else
Unbind( p.succ[ pos ] );
p.succ:= Compacted( p.succ );
fi;
fi;
od;
pos:= PositionProperty( F, x -> IsIdenticalObj( v, x ) );
Unbind( F[ pos ] );
F:= Compacted( F );
od;
Add( F, new );
fi;
od;
# Now the whole input is distributed to Q and S.
return rec( cycles:= Concatenation( Q, Reversed( S ) ),
weights:= Concatenation( Qw, Reversed( Sw ) ) );
end );
#############################################################################
##
#I InfoPackageLoading
##
## (We cannot do this in `package.gd'.)
##
DeclareInfoClass( "InfoPackageLoading" );
#############################################################################
##
#F LogPackageLoadingMessage( <severity>, <message>[, <name>] )
##
if not IsBound( TextAttr ) then
TextAttr:= "dummy";
fi;
#T needed? (decl. of GAPDoc is loaded before)
InstallGlobalFunction( LogPackageLoadingMessage, function( severity, message, currpkg... )
local i;
if Length( currpkg ) = 1 then
currpkg:= currpkg[1];
elif Length( currpkg ) > 1 then
Error("usage: LogPackageLoadingMessage( <severity>, <message>[, <name>] )");
elif IsBound( GAPInfo.PackageCurrent ) then
# This happens inside availability tests.
currpkg:= GAPInfo.PackageCurrent.PackageName;
else
currpkg:= "(unknown package)";
fi;
if IsString( message ) then
message:= [ message ];
fi;
if severity <= PACKAGE_WARNING
and UserPreference("UseColorsInTerminal") = true
and IsBound( TextAttr )
and IsRecord( TextAttr ) then
if severity = PACKAGE_ERROR then
message:= List( message,
msg -> Concatenation( TextAttr.1, msg, TextAttr.reset ) );
else
message:= List( message,
msg -> Concatenation( TextAttr.4, msg, TextAttr.reset ) );
fi;
fi;
Add( GAPInfo.PackageLoadingMessages, [ currpkg, severity, message ] );
Info( InfoPackageLoading, severity, currpkg, ": ", message[1] );
for i in [ 2 .. Length( message ) ] do
Info( InfoPackageLoading, severity, List( currpkg, x -> ' ' ),
" ", message[i] );
od;
end );
if not IsReadOnlyGlobal( "TextAttr" ) then
Unbind( TextAttr );
fi;
#############################################################################
##
#F DisplayPackageLoadingLog( [<severity>] )
##
InstallGlobalFunction( DisplayPackageLoadingLog, function( arg )
local severity, entry, message, i;
if Length( arg ) = 0 then
severity:= PACKAGE_WARNING;
else
severity:= arg[1];
fi;
for entry in GAPInfo.PackageLoadingMessages do
if severity >= entry[2] then
message:= entry[3];
Info( InfoPackageLoading, 1, entry[1], ": ", message[1] );
for i in [ 2 .. Length( message ) ] do
Info( InfoPackageLoading, 1, List( entry[1], x -> ' ' ),
" ", message[i] );
od;
fi;
od;
end );
#############################################################################
##
#F PackageAvailabilityInfo( <name>, <version>, <record>, <suggested>,
#F <checkall> )
##
InstallGlobalFunction( PackageAvailabilityInfo,
function( name, version, record, suggested, checkall )
local comp, loadinfo, 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",
"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:= "";
Name:= name;
name:= LowercaseString( name );
equal:= "";
if StartsWith( version, "=" ) then
equal:= "equal";
fi;
if name = "gap" then
# This case occurs if a package requires a particular GAP version.
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;
# 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
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;
# 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;
# 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;
# In recursive calls, regard the current package as handled,
# of course in the version in question.
currversion:= [ name ];
Add( record.AlreadyHandled, currversion );
# 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;
skip:= false;
currversion[2]:= inforec.Version;
if IsBound( inforec.Dependencies ) then
dep:= inforec.Dependencies;
else
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 ) ];
if version <> "" then
if not CompareVersionNumbers( inforec.Version, version, equal ) then
# The severity of the log message must be less than `PACKAGE_INFO',
# since we do not want to see the message when looking for reasons
# why the current package cannot be loaded.
LogPackageLoadingMessage( PACKAGE_DEBUG,
[ Concatenation( "PackageAvailabilityInfo: version ",
inforec.Version, " does not fit" ),
Concatenation( "(required: ", version, ")" ) ],
inforec.PackageName );
Append( currloadinfo.comment,
Concatenation( "version ", version, "is required, " ) );
if not checkall then
continue;
fi;
skip:= true;
else
Add( msg, Concatenation( "(required: ", version, ")" ) );
LogPackageLoadingMessage( PACKAGE_INFO, msg,
inforec.PackageName );
fi;
else
LogPackageLoadingMessage( PACKAGE_INFO, msg,
inforec.PackageName );
fi;
# Test whether the required GAP version fits.
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;
fi;
# Test whether the availability test function fits.
GAPInfo.PackageCurrent:= inforec;
test:= (not IsBound(inforec.AvailabilityTest)) or (inforec.AvailabilityTest() = true);
Unbind( GAPInfo.PackageCurrent );
if test = true then
LogPackageLoadingMessage( PACKAGE_DEBUG,
Concatenation( "PackageAvailabilityInfo: the AvailabilityTest",
" function returned 'true'" ),
inforec.PackageName );
else
LogPackageLoadingMessage( PACKAGE_INFO,
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;
skip:= true;
fi;
# Locate the `init.g' file of the package.
if Filename( [ Directory( inforec.InstallationPath ) ], "init.g" )
= fail then
LogPackageLoadingMessage( PACKAGE_WARNING,
Concatenation( "PackageAvailabilityInfo: cannot locate `",
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;
skip:= true;
fi;
record_local:= StructuralCopy( record );
wght:= 1;
pos:= PositionProperty( record_local.Weights, pair -> pair[1] = name );
if pos = fail then
Add( record_local.Weights, [ name, wght ] );
else
record_local.Weights[ pos ][2]:= wght;
fi;
# Check the dependencies of this package version.
needed:= [];
if IsBound( dep.NeededOtherPackages ) then
Append( needed, dep.NeededOtherPackages );
fi;
test:= true;
if IsEmpty( needed ) then
LogPackageLoadingMessage( PACKAGE_DEBUG,
"PackageAvailabilityInfo: no needed packages",
inforec.PackageName );
else
LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
[ "PackageAvailabilityInfo: check needed packages" ],
List( needed,
pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
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;
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( "PackageAvailabilityInfo: dependency '",
name2, "' is not satisfied" ), inforec.PackageName );
if not checkall then
# Skip the check of other dependencies.
break;
fi;
elif testpair <> true then
# The package `name2' is available but not yet loaded.
Add( record_local.Dependencies, [ name2, name ] );
fi;
od;
LogPackageLoadingMessage( PACKAGE_DEBUG,
"PackageAvailabilityInfo: check of needed packages done",
inforec.PackageName );
fi;
if test = false then
# At least one package needed by this version is not available,
if not checkall then
continue;
fi;
skip:= true;
fi;
# All checks for this version have been performed.
# Go to the next installed version if some check failed.
if skip then
continue;
fi;
# The version given by `inforec' will be taken.
# Copy the information back to the argument record.
record.InstallationPaths:= record_local.InstallationPaths;
Add( record.InstallationPaths,
[ name, [ inforec.InstallationPath, inforec.Version,
inforec.PackageName, false ] ] );
record.Dependencies:= record_local.Dependencies;
record.AlreadyHandled:= record_local.AlreadyHandled;
record.Weights:= record_local.Weights;
if suggested and IsBound( dep.SuggestedOtherPackages ) then
# Collect info about suggested packages and their dependencies,
# but without extending 'LoadInfo'.
LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
[ "PackageAvailabilityInfo: check suggested packages" ],
List( dep.SuggestedOtherPackages,
pair -> Concatenation( pair[1], " (", pair[2], ")" ) ) ),
inforec.PackageName );
for pair in dep.SuggestedOtherPackages do
name2:= LowercaseString( pair[1] );
# 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 test <> false then
record.InstallationPaths:= record_local.InstallationPaths;
record.Dependencies:= record_local.Dependencies;
record.AlreadyHandled:= record_local.AlreadyHandled;
record.Weights:= record_local.Weights;
fi;
fi;
od;
LogPackageLoadingMessage( PACKAGE_DEBUG,
"PackageAvailabilityInfo: check of suggested packages done",
inforec.PackageName );
fi;
# Print a warning if the package should better be upgraded.
if IsBound( GAPInfo.PackagesRestrictions.( name ) ) then
GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
fi;
#T component name OnLoad:
#T shouldn't this be done only if the package is actually loaded?
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( "PackageAvailabilityInfo: version ",
inforec.Version, " is available" ),
inforec.PackageName );
return inforec.InstallationPath;
od;
# No info record satisfies the requirements.
if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
inforec:= First( GAPInfo.PackagesInfoRefuseLoad,
r -> LowercaseString( r.PackageName ) = name );
if inforec <> fail then
# Some versions are installed but all were refused.
GAPInfo.PackagesRestrictions.( name ).OnLoad( inforec );
fi;
fi;
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( "PackageAvailabilityInfo: ",
"no installed version fits" ), Name );
return false;
end );
#############################################################################
##
#F TestPackageAvailability( <name>[, <version>][, <checkall>] )
##
InstallGlobalFunction( TestPackageAvailability, function( arg )
local name, version, checkall, result;
# Get the arguments.
name:= LowercaseString( arg[1] );
version:= "";
checkall:= false;
if Length( arg ) = 2 then
if IsString( arg[2] ) then
version:= arg[2];
elif arg[2] = true then
checkall:= true;
fi;
elif Length( arg ) = 3 then
if IsString( arg[2] ) then
version:= arg[2];
fi;
if arg[3] = true then
checkall:= true;
fi;
fi;
# Ignore suggested packages.
result:= PackageAvailabilityInfo( name, version, rec(), false,
checkall );
if result = false then
return fail;
else
return result;
fi;
end );
#############################################################################
##
#F IsPackageLoaded( <name>[, <version>] )
##
InstallGlobalFunction( IsPackageLoaded, function( name, version... )
local result;
if Length(version) > 0 then
version := version[1];
fi;
result := IsPackageMarkedForLoading( name, version );
if result then
# check if the package actually completed loading
result := GAPInfo.PackagesLoaded.( LowercaseString( name ) )[4];
fi;
return result;
end );
#############################################################################
##
#F IsPackageMarkedForLoading( <name>, <version> )
##
InstallGlobalFunction( IsPackageMarkedForLoading, function( name, version )
local equal;
equal:= "";
if 0 < Length( version ) and version[1] = '=' then
equal:= "equal";
fi;
name:= LowercaseString( name );
return IsBound( GAPInfo.PackagesLoaded.( name ) )
and CompareVersionNumbers( GAPInfo.PackagesLoaded.( name )[2],
version, equal );
end );
#############################################################################
##
#F DefaultPackageBannerString( <inforec> )
##
DeclareUserPreference( rec(
name:= "ShortBanners",
description:= [
"If this option is set to <K>true</K>, package banners printed during \
loading will only show the name, version and description of a package."
],
default:= false,
values:= [ true, false ],
multi:= false,
) );
InstallGlobalFunction( DefaultPackageBannerString,
function( inforec, useShortBanner... )
local len, sep, i, str, authors, maintainers, contributors, printPersons;
if Length( useShortBanner ) = 0 then
useShortBanner := false;
elif Length( useShortBanner ) = 1 then
useShortBanner := useShortBanner[1];
else
Error( "DefaultPackageBannerString must be called with at most two arguments" );
fi;
# Start with a row of `-' signs.
len:= SizeScreen()[1] - 3;
if GAPInfo.TermEncoding = "UTF-8" then
# The unicode character we use takes up 3 bytes in UTF-8 encoding,
# hence we must adjust the length accordingly.
sep:= "─";
i:= 1;
while 2 * i <= len do
Append( sep, sep );
i:= 2 * i;
od;
Append( sep, sep{ [ 1 .. 3 * ( len - i ) ] } );
else
sep:= ListWithIdenticalEntries( len, '-' );
fi;
Add( sep, '\n' );
str:= "";
# Add package name and version number.
if IsBound( inforec.PackageName ) and IsBound( inforec.Version ) then
Append( str, Concatenation(
"Loading ", inforec.PackageName, " ", inforec.Version ) );
fi;
# Add the long title.
if IsBound( inforec.PackageDoc ) and IsBound( inforec.PackageDoc[1] ) and
IsBound( inforec.PackageDoc[1].LongTitle ) and
not IsEmpty( inforec.PackageDoc[1].LongTitle ) then
Append( str, Concatenation(
" (", inforec.PackageDoc[1].LongTitle, ")" ) );
fi;
Add( str, '\n' );
if not useShortBanner then
# Add info about the authors and/or maintainers
printPersons := function( role, persons )
local fill, person;
fill:= ListWithIdenticalEntries( Length(role), ' ' );
Append( str, role );
for i in [ 1 .. Length( persons ) ] do
person:= persons[i];
if IsBound( person.FirstNames ) then
Append( str, person.FirstNames );
Append( str, " " );
fi;
Append( str, person.LastName );
if IsBound( person.WWWHome ) then
Append( str, Concatenation( " (", person.WWWHome, ")" ) );
elif IsBound( person.Email ) then
Append( str, Concatenation( " (", person.Email, ")" ) );
fi;
if i = Length( persons ) then
Append( str, ".\n" );
elif i = Length( persons )-1 then
if i = 1 then
Append( str, " and\n" );
else
Append( str, ", and\n" );
fi;
Append( str, fill );
else
Append( str, ",\n" );
Append( str, fill );
fi;
od;
end;
if IsBound( inforec.Persons ) then
authors:= Filtered( inforec.Persons, x -> x.IsAuthor );
if not IsEmpty( authors ) then
printPersons( "by ", authors );
fi;
contributors:= Filtered( inforec.Persons, x -> not x.IsAuthor and not x.IsMaintainer );
if not IsEmpty( contributors ) then
Append( str, "with contributions by:\n");
printPersons( " ", contributors );
fi;
maintainers:= Filtered( inforec.Persons, x -> x.IsMaintainer );
if not IsEmpty( maintainers ) and authors <> maintainers then
Append( str, "maintained by:\n");
printPersons( " ", maintainers );
fi;
fi;
# Add info about the home page of the package.
if IsBound( inforec.PackageWWWHome ) then
Append( str, "Homepage: " );
Append( str, inforec.PackageWWWHome );
Append( str, "\n" );
fi;
# Add info about the issue tracker of the package.
if IsBound( inforec.IssueTrackerURL ) then
Append( str, "Report issues at " );
Append( str, inforec.IssueTrackerURL );
Append( str, "\n" );
fi;
str := Concatenation(sep, str, sep);
fi;
# temporary hack, in some package names with umlauts are in HTML encoding
str := RecodeForCurrentTerminal(str);
str:= ReplacedString( str, "ä", RecodeForCurrentTerminal("ä") );
str:= ReplacedString( str, "ö", RecodeForCurrentTerminal("ö") );
str:= ReplacedString( str, "ü", RecodeForCurrentTerminal("ü") );
return str;
end );
#############################################################################
##
#F DirectoriesPackagePrograms( <name> )
##
InstallGlobalFunction( DirectoriesPackagePrograms, function( name )
local info, installationpath;
# We are not allowed to call
# `InstalledPackageVersion', `TestPackageAvailability' etc.
name:= LowercaseString( name );
info:= PackageInfo( name );
if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
# The package is already loaded.
installationpath:= GAPInfo.PackagesLoaded.( name )[1];
elif IsBound( GAPInfo.PackageCurrent ) and
LowercaseString( GAPInfo.PackageCurrent.PackageName ) = name then
# The package in question is currently going to be loaded.
installationpath:= GAPInfo.PackageCurrent.InstallationPath;
elif 0 < Length( info ) then
# Take the installed package with the highest version
# that has been found first in the root paths.
installationpath:= info[1].InstallationPath;
else
# This package is not known.
return [];
fi;
return [ Directory( Concatenation( installationpath, "/bin/",
GAPInfo.Architecture, "/" ) ) ];
end );
#############################################################################
##
#F DirectoriesPackageLibrary( <name>[, <path>] )
##
InstallGlobalFunction( DirectoriesPackageLibrary, function( arg )
local name, path, info, installationpath, tmp;
if IsEmpty(arg) or 2 < Length(arg) then
Error( "usage: DirectoriesPackageLibrary( <name>[, <path>] )" );
elif not ForAll(arg, IsString) then
Error( "string argument(s) expected" );
fi;
name:= LowercaseString( arg[1] );
if '\\' in name or ':' in name then
Error( "<name> must not contain '\\' or ':'" );
elif 1 = Length(arg) then
path := "lib";
else
path := arg[2];
fi;
# We are not allowed to call
# `InstalledPackageVersion', `TestPackageAvailability' etc.
info:= PackageInfo( name );
if IsBound( GAPInfo.PackagesLoaded.( name ) ) then
# The package is already loaded.
installationpath:= GAPInfo.PackagesLoaded.( name )[1];
elif IsBound( GAPInfo.PackageCurrent ) and
LowercaseString( GAPInfo.PackageCurrent.PackageName ) = name then
# The package in question is currently going to be loaded.
installationpath:= GAPInfo.PackageCurrent.InstallationPath;
elif 0 < Length( info ) then
# Take the installed package with the highest version
# that has been found first in the root paths.
installationpath:= info[1].InstallationPath;
else
# This package is not known.
return [];
fi;
tmp:= Filename( Directory( installationpath ), path );
if IsDirectoryPath( tmp ) = true then
return [ Directory( tmp ) ];
fi;
return [];
end );
#############################################################################
##
#F ReadPackage( [<name>, ]<file> )
#F RereadPackage( [<name>, ]<file> )
##
BindGlobal( "_ReadPackage", function( arg, error )
local pos, relpath, pkgname, namespace, filename;
# Note that we cannot use `ReadAndCheckFunc' because this calls
# `READ_GAP_ROOT', but here we have to read the file in one of those
# directories where the package version resides that has been loaded
# or (at least currently) would be loaded.
if Length( arg ) = 1 then
# Guess the package name.
pos:= Position( arg[1], '/' );
if pos = fail then
ErrorNoReturn(arg[1], " is not a filename in the form 'package/filepath'");
fi;
pkgname:= arg[1]{ [ 1 .. pos-1 ] };
relpath:= arg[1]{ [ pos+1 .. Length( arg[1] ) ] };
elif Length( arg ) = 2 then
pkgname:= arg[1];
relpath:= arg[2];
else
Error( "expected 1 or 2 arguments" );
fi;
pkgname:= LowercaseString( pkgname );
namespace := GAPInfo.PackagesInfo.(pkgname)[1].PackageName;
# Note that `DirectoriesPackageLibrary' finds the file relative to the
# installation path of the info record chosen in `LoadPackage'.
filename:= Filename( DirectoriesPackageLibrary( pkgname, "" ), relpath );
if filename <> fail and IsReadableFile( filename ) then
ENTER_NAMESPACE(namespace);
Read( filename );
LEAVE_NAMESPACE();
return true;
elif error then
Info(InfoWarning, 1, "ReadPackage could not read <", pkgname, ">/", relpath, "\n");
fi;
return false;
end );
InstallGlobalFunction( ReadPackage, function( arg )
return _ReadPackage( arg, true );
end );
InstallGlobalFunction( RereadPackage, function( arg )
local res;
MakeReadWriteGlobal( "REREADING" );
REREADING:= true;
MakeReadOnlyGlobal( "REREADING" );
res:= _ReadPackage( arg, false );
MakeReadWriteGlobal( "REREADING" );
REREADING:= false;
MakeReadOnlyGlobal( "REREADING" );
return res;
end );
#############################################################################
##
#F LoadPackageDocumentation( <info> )
##
## In versions before 4.5, a second argument was required.
## For the sake of backwards compatibility, we do not forbid a second
## argument, but we ignore it.
## (In later versions, we may forbid the second argument.)
##
InstallGlobalFunction( LoadPackageDocumentation, function( arg )
local info, short, pkgdoc, long, sixfile;
info:= arg[1];
# Load all books for the package.
for pkgdoc in info.PackageDoc do
# Fetch the names.
if IsBound( pkgdoc.LongTitle ) then
long:= pkgdoc.LongTitle;
else
long:= Concatenation( "GAP Package `", info.PackageName, "'" );
fi;
short:= pkgdoc.BookName;
if not IsBound( GAPInfo.PackagesLoaded.( LowercaseString(
info.PackageName ) ) ) then
short:= Concatenation( short, " (not loaded)" );
fi;
# Check that the `manual.six' file is available.
sixfile:= Filename( [ Directory( info.InstallationPath ) ],
pkgdoc.SixFile );
if sixfile = fail then
LogPackageLoadingMessage( PACKAGE_INFO,
Concatenation( [ "book `", pkgdoc.BookName,
"': no manual index file `",
pkgdoc.SixFile, "', ignored" ] ),
info.PackageName );
else
# Finally notify the book via its directory.
#T Here we assume that this is the directory that contains also `manual.six'!
HELP_ADD_BOOK( short, long,
Directory( sixfile{ [ 1 .. Length( sixfile )-10 ] } ) );
fi;
od;
end );
#############################################################################
##
#F LoadPackage_ReadImplementationParts( <secondrun>, <banner> )
##
BindGlobal( "LoadPackage_ReadImplementationParts",
function( secondrun, banner )
local pair, info, bannerstring, pkgname, namespace;
for pair in secondrun do
namespace := pair[1].PackageName;
pkgname := LowercaseString( namespace );
if pair[2] <> fail then
GAPInfo.PackageCurrent:= pair[1];
LogPackageLoadingMessage( PACKAGE_DEBUG,
"start reading file 'read.g'",
namespace );
ENTER_NAMESPACE(namespace);
Read( pair[2] );
LEAVE_NAMESPACE();
Unbind( GAPInfo.PackageCurrent );
LogPackageLoadingMessage( PACKAGE_DEBUG,
"finish reading file 'read.g'",
namespace );
fi;
# mark the package as completely loaded
GAPInfo.PackagesLoaded.(pkgname)[4] := true;
MakeImmutable( GAPInfo.PackagesLoaded.(pkgname) );
od;
# Show the banners.
if banner then
for pair in secondrun do
info:= pair[1];
# If the component `BannerString' is bound in `info' then we print
# this string, otherwise we print the default banner string.
if UserPreference( "ShortBanners" ) then
bannerstring:= DefaultPackageBannerString( info, true );
elif IsBound( info.BannerFunction ) then
bannerstring:= RecodeForCurrentTerminal(info.BannerFunction(info));
elif IsBound( info.BannerString ) then
bannerstring:= RecodeForCurrentTerminal(info.BannerString);
else
bannerstring:= DefaultPackageBannerString( info );
fi;
# Suppress output formatting to avoid troubles with umlauts,
# accents etc. in the banner.
PrintWithoutFormatting( bannerstring );
od;
fi;
end );
#############################################################################
##
#F GetPackageNameForPrefix( <prefix> ) . . . . . . . . show list of matches
#F or single match directly
##
## Compute all names of installed packages that match the prefix <prefix>.
## In case of a unique match return this match,
## otherwise print an info message about the matches and return <prefix>.
##
## This function is called by `LoadPackage'.
##
BindGlobal( "GetPackageNameForPrefix", function( prefix )
local len, lowernames, name, allnames, indent, pos, sep;
len:= Length( prefix );
lowernames:= [];
for name in Set( RecNames( GAPInfo.PackagesInfo ) ) do
if Length( prefix ) <= Length( name ) and
name{ [ 1 .. len ] } = prefix then
Add( lowernames, name );
fi;
od;
if IsEmpty( lowernames ) then
# No package name matches.
return prefix;
fi;
allnames:= List( lowernames,
nam -> GAPInfo.PackagesInfo.( nam )[1].PackageName );
if Length( allnames ) = 1 then
# There is one exact match.
LogPackageLoadingMessage( PACKAGE_DEBUG, Concatenation(
[ "replace prefix '", prefix, "' by the unique completion '",
allnames[1], "'" ] ), allnames[1] );
return lowernames[1];
fi;
# Several package names match.
if 0 < InfoLevel( InfoPackageLoading ) then
Print( "#I Call 'LoadPackage' with one of the following strings:\n" );
len:= SizeScreen()[1] - 6;
indent:= "#I ";
Print( indent );
pos:= Length( indent );
sep:= "";
for name in allnames do
Print( sep );
pos:= pos + Length( sep );
if len < pos + Length( name ) then
Print( "\n", indent );
pos:= Length( indent );
fi;
Print( "\"", name, "\"" );
pos:= pos + Length( name ) + 2;
sep:= ", ";
od;
Print( ".\n" );
fi;
return prefix;
end );
#############################################################################
##
#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, loadinfo;
# Get the arguments.
if Length( arg ) = 0 then
name:= "";
else
name:= arg[1];
if not IsString( name ) then
Error( "<name> must be a string" );
fi;
name:= LowercaseString( name );
fi;
if not IsBound( GAPInfo.PackagesInfo.( name ) ) then
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;
# The package is available, fetch the name for messages.
Name:= GAPInfo.PackagesInfo.( name )[1].PackageName;
version:= "";
banner:= not GAPInfo.CommandLineOptions.q and
not GAPInfo.CommandLineOptions.b;
if 1 < Length( arg ) then
if IsString( arg[2] ) then
version:= arg[2];
if 2 < Length( arg ) then
banner:= banner and not ( arg[3] = false );
fi;
else
banner:= banner and not ( arg[2] = false );
fi;
fi;
loadsuggested:= ( ValueOption( "OnlyNeeded" ) <> true );
# Print a warning if `LoadPackage' is called inside a
# `LoadPackage' call.
if not IsBound( GAPInfo.LoadPackageLevel ) then
GAPInfo.LoadPackageLevel:= 0;
fi;
GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel + 1;
if GAPInfo.LoadPackageLevel <> 1 then
if IsBound( GAPInfo.PackageCurrent ) then
msg:= GAPInfo.PackageCurrent.PackageName;
else
msg:= "?";
fi;
LogPackageLoadingMessage( PACKAGE_WARNING,
[ Concatenation( "Do not call `LoadPackage( \"", name,
"\", ... )' in the package file" ),
Concatenation( INPUT_FILENAME(), "," ),
"use `IsPackageMarkedForLoading' instead" ], msg );
fi;
# Start logging.
msg:= "entering LoadPackage ";
if not loadsuggested then
Append( msg, " (omitting suggested packages)" );
fi;
LogPackageLoadingMessage( PACKAGE_DEBUG, msg, Name );
# Test whether the package is available,
# and compute the dependency information.
depinfo:= rec( LoadInfo:= loadinfo );
path:= PackageAvailabilityInfo( name, version, depinfo, loadsuggested,
false );
if not IsString( path ) then
if path = false then
path:= fail;
fi;
# The result is either `true' (the package is already loaded)
# or `fail' (the package cannot be loaded).
if path = true then
LogPackageLoadingMessage( PACKAGE_DEBUG,
"return from LoadPackage, package was already loaded", Name );
else
LogPackageLoadingMessage( PACKAGE_DEBUG,
"return from LoadPackage, package is not available", Name );
if banner then
if InfoLevel(InfoPackageLoading) < 4 then
Info(InfoWarning,1, Name, " package is not available. To see further details, enter");
Info(InfoWarning,1, "SetInfoLevel(InfoPackageLoading,4); and try to load the package again.");
fi;
fi;
fi;
GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
return path;
fi;
# Suspend reordering of methods following InstallTrueMethod
# because it would slow things down too much
SuspendMethodReordering();
# Compute the order in which the packages are loaded.
# For each set of packages with cyclic dependencies,
# we will first read all `init.g' files
# and afterwards all `read.g' files.
if IsEmpty( depinfo.Dependencies ) then
order:= rec( cycles:= [ [ name ] ],
weights:= [ depinfo.Weights[1][2] ] );
else
order:= LinearOrderByPartialWeakOrder( depinfo.Dependencies,
depinfo.Weights );
fi;
# paths:= TransposedMatMutable( depinfo.InstallationPaths );
# (TransposedMatMutable is not yet available here ...)
paths:= [ [], [] ];
for pair in depinfo.InstallationPaths do
Add( paths[1], pair[1] );
Add( paths[2], pair[2] );
od;
SortParallel( paths[1], paths[2] );
secondrun:= [];
for i in [ 1 .. Length( order.cycles ) ] do
cycle:= order.cycles[i];
# First mark all packages in the current cycle as loaded,
# in order to avoid that an occasional call of `LoadPackage'
# inside the package code causes the files to be read more than once.
for pkgname in cycle do
pos:= PositionSorted( paths[1], pkgname );
# the following entry is made immutable in LoadPackage_ReadImplementationParts
GAPInfo.PackagesLoaded.( pkgname ):= paths[2][ pos ];
od;
if loadsuggested then
msg:= "start loading needed/suggested/self packages";
else
msg:= "start loading needed/self packages";
fi;
LogPackageLoadingMessage( PACKAGE_DEBUG,
Concatenation( [ msg ], cycle ),
Name );
for pkgname in cycle do
pos:= PositionSorted( paths[1], pkgname );
info:= First( PackageInfo( pkgname ),
r -> r.InstallationPath = paths[2][ pos ][1] );
if not ValidatePackageInfo(info) then
Print("#E Validation of package ", pkgname, " from ", info.InstallationPath, " failed\n");
fi;
# Notify the documentation (for the available version).
LoadPackageDocumentation( info );
# Notify extensions provided by the package.
if IsBound( info.Extensions ) then
for entry in info.Extensions do
LogPackageLoadingMessage( PACKAGE_DEBUG,
Concatenation( "notify extension ", entry.filename ),
pkgname );
r:= ShallowCopy( entry );
r.providedby:= pkgname;
Add( GAPInfo.PackageExtensionsPending, Immutable( r ) );
od;
fi;
# Read the `init.g' files.
LogPackageLoadingMessage( PACKAGE_DEBUG,
"start reading file 'init.g'",
info.PackageName );
GAPInfo.PackageCurrent:= info;
ReadPackage( pkgname, "init.g" );
Unbind( GAPInfo.PackageCurrent );
LogPackageLoadingMessage( PACKAGE_DEBUG,
"finish reading file 'init.g'",
info.PackageName );
filename:= Filename( [ Directory( info.InstallationPath ) ],
"read.g" );
Add( secondrun, [ info, filename ] );
od;
# Read the `read.g' files collected up to now.
# Afterwards show the banners.
# (We have delayed this until now because it uses functionality
# from the package GAPDoc.)
# Note that no banners are printed during autoloading.
LoadPackage_ReadImplementationParts( secondrun, banner );
secondrun:= [];
od;
# Load those package extensions whose condition is satisfied.
for i in [ 1 .. Length( GAPInfo.PackageExtensionsPending ) ] do
entry:= GAPInfo.PackageExtensionsPending[i];
if ForAll( entry.needed, l -> IsPackageLoaded( l[1], l[2] ) ) then
ReadPackage( entry.providedby, entry.filename );
Add( GAPInfo.PackageExtensionsLoaded, entry );
Unbind( GAPInfo.PackageExtensionsPending[i] );
LogPackageLoadingMessage( PACKAGE_DEBUG,
Concatenation( "load extension ", entry.filename ),
entry.providedby );
fi;
od;
GAPInfo.PackageExtensionsPending:= Compacted( GAPInfo.PackageExtensionsPending );
LogPackageLoadingMessage( PACKAGE_DEBUG, "return from LoadPackage",
Name );
GAPInfo.LoadPackageLevel:= GAPInfo.LoadPackageLevel - 1;
ResumeMethodReordering();
return true;
end );
#############################################################################
##
#F LoadAllPackages()
##
InstallGlobalFunction( LoadAllPackages, function()
SuspendMethodReordering();
if ValueOption( "reversed" ) = true then
List( Reversed( RecNames( GAPInfo.PackagesInfo ) ), LoadPackage );
else
List( RecNames( GAPInfo.PackagesInfo ), LoadPackage );
fi;
ResumeMethodReordering();
end );
#############################################################################
##
#F SetPackagePath( <pkgname>, <pkgpath> )
##
InstallGlobalFunction( SetPackagePath, function( pkgname, pkgpath )
local pkgdir, file, record;
InitializePackagesInfoRecords();
pkgname:= LowercaseString( pkgname );
NormalizeWhitespace( pkgname );
if IsBound( GAPInfo.PackagesLoaded.( pkgname ) ) then
# compare using `Directory` to expand "~" and add trailing "/"
if Directory( GAPInfo.PackagesLoaded.( pkgname )[1] ) = Directory( pkgpath ) then
return;
fi;
Error( "another version of package ", pkgname, " is already loaded" );
fi;
pkgdir:= Directory( pkgpath );
file:= Filename( [ pkgdir ], "PackageInfo.g" );
if file = fail then
return;
fi;
Unbind( GAPInfo.PackageInfoCurrent );
Read( file );
record:= GAPInfo.PackageInfoCurrent;
Unbind( GAPInfo.PackageInfoCurrent );
if pkgname <> NormalizedWhitespace( LowercaseString(
record.PackageName ) ) then
Error( "found package ", record.PackageName, " not ", pkgname,
" in ", pkgpath );
fi;
if IsBound( GAPInfo.PackagesRestrictions.( pkgname ) )
and GAPInfo.PackagesRestrictions.( pkgname ).OnInitialization(
record ) = false then
Add( GAPInfo.PackagesInfoRefuseLoad, record );
else
record.InstallationPath:= Filename( [ pkgdir ], "" );
if not IsBound( record.PackageDoc ) then
record.PackageDoc:= [];
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet)
]
|