diff --git a/dev/ci.sh b/dev/ci.sh
index f61e93b349..a39c816d21 100755
--- a/dev/ci.sh
+++ b/dev/ci.sh
@@ -99,11 +99,9 @@ testmockpkg () {
cd "$mockpkg_dir"
echo_and_run ./configure "$gaproot"
echo_and_run $MAKE V=1
- # trick to make it easy to load the package in GAP
- rm -f pkg && ln -sf . pkg
# try to load the kernel extension
cd "$gaproot"
- echo_and_run $gap -A -l "$mockpkg_dir;" "$mockpkg_dir/tst/testall.g"
+ echo_and_run $gap -A --packagedirs "$mockpkg_dir" "$mockpkg_dir/tst/testall.g"
}
diff --git a/doc/ref/files.xml b/doc/ref/files.xml
index 4da8aca5af..75cb5f68b3 100644
--- a/doc/ref/files.xml
+++ b/doc/ref/files.xml
@@ -81,9 +81,9 @@ directories. For example when ⪆ wants to read its library file
GAPInfo.RootPaths until it finds the path of an existing file.
The first file found this way is read.
-Furthermore, ⪆ looks for available packages by examining the
-subdirectories pkg/ in each of the directories in
-GAPInfo.RootPaths.
+Any subdirectories named pkg/ in one of the directories in GAPInfo.RootPaths
+are added to GAPInfo.PackageDirectories (see ),
+which controls where ⪆ looks for available packages.
The root directories are specified via one or several of the
-l paths command line options, see .
@@ -95,11 +95,36 @@ This directory can be used to tell ⪆ about personal preferences,
to always load some additional code, to install additional packages,
or to overwrite some ⪆ files. See
for more information how to do this.
+After ⪆ has been started, one can add additional root directories
+via the function .
+
+
+GAP Package Directories
+GAPInfo.PackageDirectories
+
+When ⪆ is started it determines a list of directories potentially
+containing packages. We refer to these as the ⪆ package directories.
+In a running ⪆ session this list can be found in GAPInfo.PackageDirectories.
+
+Every subdirectory pkg in a ⪆ root directory is automatically
+added to this list. Further package directories can be specified via one or several
+--packagedirs paths command line options, see ,
+or after ⪆ has been started via the function .
+The order of the directories in GAPInfo.PackageDirectories is as follows:
+first the package directories specified via the command line option --packagedirs,
+then the subdirectories pkg of the ⪆ root directories that were known at startup in the
+same order, and finally the directories added after ⪆ has been started.
+
+⪆ looks for available packages by examining each of the directories in
+GAPInfo.PackageDirectories.
+
+
+
Directories
diff --git a/doc/ref/gappkg.xml b/doc/ref/gappkg.xml
index 03751f2757..1fa68f75d5 100644
--- a/doc/ref/gappkg.xml
+++ b/doc/ref/gappkg.xml
@@ -96,6 +96,7 @@ that is they will be loaded automatically when ⪆ starts
<#Include Label="SetPackagePath">
<#Include Label="ExtendRootDirectories">
+<#Include Label="ExtendPackageDirectories">
<#Include Label="DisplayPackageLoadingLog">
diff --git a/doc/ref/run.xml b/doc/ref/run.xml
index 88101cfdae..c875482309 100644
--- a/doc/ref/run.xml
+++ b/doc/ref/run.xml
@@ -269,6 +269,27 @@ It is not possible to use &GAP; without the library files, so you must
not ignore this warning. You should leave &GAP; and start it again,
specifying the correct root path using the -l option.
+--packagedirs
+--packagedirs path_list
+-
+can be used to add paths to &GAP;'s list of package directories
+(see
).
+The list always contains all subdirectories pkg in a &GAP; root directory.
+
+path_list should be a list of directories separated by semicolons.
+No whitespace is permitted before or after a semicolon, and the first and
+last character of path_list may not be a semicolon.
+After &GAP; has completed its startup procedure and
+displays the prompt, the list of package directories can be seen in the
+variable GAPInfo.PackageDirectories,
+see .
+
+Usually this option is used inside a startup script to specify
+where additional &GAP; packages are located on the system.
+The --packagedirs option can also be used by individual users to tell &GAP;
+about additional &GAP; packages, without the need to set up a complete root
+directory structure.
+
-M
-M
-
diff --git a/lib/package.gd b/lib/package.gd
index 8136cc518d..cc1de684f0 100644
--- a/lib/package.gd
+++ b/lib/package.gd
@@ -859,7 +859,8 @@ DeclareGlobalFunction( "LoadPackage" );
##
## See
for a way to force the loading of a
## prescribed package version.
-## See also for a method of adding
+## See also and
+## for methods of adding
## directories containing packages after &GAP; has been started.
##
## <#/GAPDoc>
@@ -947,6 +948,29 @@ DeclareGlobalFunction( "SetPackagePath" );
##
DeclareGlobalFunction( "ExtendRootDirectories" );
+
+#############################################################################
+##
+#F ExtendPackageDirectories( )
+##
+## <#GAPDoc Label="ExtendPackageDirectories">
+##
+##
+##
+##
+## Let paths be a list of strings that denote paths to intended
+## &GAP; package directories (see ).
+## The function adds these paths to
+## the global list GAPInfo.PackageDirectories and calls the initialization of
+## available &GAP; packages,
+## such that later calls to will find the &GAP;
+## packages that are contained in the directories given by paths.
+##
+##
+## <#/GAPDoc>
+##
+DeclareGlobalFunction( "ExtendPackageDirectories" );
+
#############################################################################
##
#F InstalledPackageVersion( )
diff --git a/lib/package.gi b/lib/package.gi
index e7780524bf..0a0068a7d6 100644
--- a/lib/package.gi
+++ b/lib/package.gi
@@ -286,7 +286,7 @@ end );
## In earlier versions, this function had an argument; now we ignore it.
##
InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
- local pkgdirs, pkgdir, ignore, name, files, record, r;
+ local pkgdirs, pkgdir, pkgdirstrs, ignore, name, file, files, record, r;
if IsBound( GAPInfo.PackagesInfoInitialized ) and
GAPInfo.PackagesInfoInitialized = true then
@@ -300,8 +300,28 @@ InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
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
+ 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" );
@@ -327,7 +347,7 @@ InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
# 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 pkgdirs do
+ for pkgdir in GAPInfo.PackageDirectories do
if IsBound( GAPInfo.ExcludeFromAutoload ) then
UniteSet( GAPInfo.ExcludeFromAutoload,
@@ -335,15 +355,21 @@ InstallGlobalFunction( InitializePackagesInfoRecords, function( arg )
LowercaseString ) );
fi;
- # Loop over subdirectories of this package directory.
- for name in Set( DirectoryContents( Filename( pkgdir, "" ) ) ) do
+ # 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 );
+ ## Get all package dirs
+ files := FindPackageInfosInSubdirectories( pkgdir, name );
- AddPackageInfos( files, pkgdir, ignore );
+ AddPackageInfos( files, pkgdir, ignore );
- od;
+ od;
+ fi;
od;
# Sort the available info records by their version numbers.
@@ -1958,6 +1984,35 @@ InstallGlobalFunction( ExtendRootDirectories, function( rootpaths )
end );
+#############################################################################
+##
+#F ExtendPackageDirectories( )
+##
+InstallGlobalFunction( ExtendPackageDirectories, function( paths_or_dirs )
+ local p, changed;
+ changed:= false;
+ for p in paths_or_dirs do
+ if IsString( p ) then
+ p:= Directory( p );
+ elif not IsDirectory( p ) then
+ Error("input must be a list of path strings or directory objects");
+ fi;
+ if not p in GAPInfo.PackageDirectories then
+ Add( GAPInfo.PackageDirectories, p );
+ changed:= true;
+ fi;
+ od;
+ if changed then
+ # Reread the package information.
+ if IsBound( GAPInfo.PackagesInfoInitialized ) and
+ GAPInfo.PackagesInfoInitialized = true then
+ GAPInfo.PackagesInfoInitialized:= false;
+ InitializePackagesInfoRecords();
+ fi;
+ fi;
+ end );
+
+
#############################################################################
##
#F InstalledPackageVersion( )
diff --git a/lib/system.g b/lib/system.g
index d79aca2008..d3c5e5b110 100644
--- a/lib/system.g
+++ b/lib/system.g
@@ -95,6 +95,9 @@ BIND_GLOBAL( "GAPInfo", rec(
"directories to the end/start of existing list",
"of root paths" ] ),
rec( short:= "r", default := false, help := ["disable/enable user GAP root dir", "GAPInfo.UserGapRoot"] ),
+ rec( long := "packagedirs", default := [], arg := "",
+ help := [ "add additional GAP directory paths",
+ "Directories are separated using ';'." ] ),
,
rec( section:= ["Loading:"] ),
rec( short:= "A", default := false, help := ["disable/enable autoloading of suggested", "GAP packages"] ),
@@ -302,6 +305,7 @@ CallAndInstallPostRestore( function()
# paths
GAPInfo.RootPaths:= GAPInfo.KernelInfo.GAP_ROOT_PATHS;
+ GAPInfo.PackageDirectories := [];
if IsBound(GAPInfo.SystemEnvironment.HOME) then
GAPInfo.UserHome := GAPInfo.SystemEnvironment.HOME;
else
diff --git a/tst/testinstall/package.tst b/tst/testinstall/package.tst
index cd364a81b4..10e515fc9c 100644
--- a/tst/testinstall/package.tst
+++ b/tst/testinstall/package.tst
@@ -1,4 +1,4 @@
-#@local entry,equ,pair,sml,oldTermEncoding,pkginfo,info,tmp_dir,mockpkgpath,old_warning_level,p,n,filename,IsDateFormatValid,loadinfo,eval_loadinfo
+#@local entry,equ,pair,sml,oldTermEncoding,pkginfo,info,mockpkgpath,old_warning_level,p,n,filename,IsDateFormatValid,loadinfo,eval_loadinfo
gap> START_TEST("package.tst");
# CompareVersionNumbers( , [, \"equal\"] )
@@ -380,17 +380,9 @@ false
gap> IsPackageLoaded("mockpkg", ">=2.0");
false
-# load mockpkg via a symlink in a directory called `pkg`
-# so we can test ExtendRootDirectories below
-# first create a temporary directory for all of this
-gap> tmp_dir := DirectoryTemporary( );;
-
-# create a subdirectory `/pkg`
-gap> Exec( Concatenation( "mkdir -p ", Filename( tmp_dir, "/pkg" ) ) );
-
-# make `/pkg/mockpkg` a symlink to `tst/mockpkg`
-gap> Exec( Concatenation( "ln -sfn ", Filename( DirectoriesLibrary("tst/mockpkg"), "" )," ", Filename( tmp_dir, "pkg/mockpkg" ) ) );
-gap> mockpkgpath := Directory( Filename( tmp_dir, "pkg/mockpkg" ) );;
+# load mockpkg first via SetPackagePath and later via
+# ExtendPackageDirectories
+gap> mockpkgpath := DirectoriesLibrary("tst/mockpkg")[1];;
gap> ValidatePackageInfo(Filename(mockpkgpath, "PackageInfo.g"));
true
@@ -635,12 +627,8 @@ false
gap> IsPackageLoaded("mockpkg", ">=2.0");
false
-# now add the temporary directory created above as a new root directory
-gap> filename:= ShallowCopy( Filename( tmp_dir, "" ) );;
-gap> while EndsWith( filename, "/" ) do Remove( filename ); od;
-gap> ExtendRootDirectories( [ filename ] );
-gap> ForAll( GAPInfo.RootPaths, x -> EndsWith( x, "/" ) );
-true
+# now add the directory with mockpkgpath as a new package directory
+gap> ExtendPackageDirectories( [ mockpkgpath ] );
# make sure that the newly discovered installation path matches
# the path from which mockpkg was loaded above
@@ -649,8 +637,7 @@ gap> Last( GAPInfo.PackagesInfo.mockpkg ).InstallationPath =
true
#
-gap> SetPackagePath( "mockpkg", Filename( tmp_dir, "pkg/mockpkg" ) );
-gap> SetPackagePath( "mockpkg", Filename( tmp_dir, "pkg/mockpkg/" ) );
+gap> SetPackagePath( "mockpkg", Filename( mockpkgpath, "" ) );
gap> SetPackagePath( "mockpkg", "/some/other/directory" );
Error, another version of package mockpkg is already loaded