From d2dab76e883fd44c7165c6faa6fe7e633ee246ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Mon, 6 Jan 2025 15:13:18 +0100 Subject: [PATCH] Add --packagedirs and ExtendPackageDirectories() (#5873) ... to make it easier to use custom packages --- dev/ci.sh | 4 +- doc/ref/files.xml | 31 ++++++++++++++-- doc/ref/gappkg.xml | 1 + doc/ref/run.xml | 21 +++++++++++ lib/package.gd | 26 ++++++++++++- lib/package.gi | 73 ++++++++++++++++++++++++++++++++----- lib/system.g | 4 ++ tst/testinstall/package.tst | 27 ++++---------- 8 files changed, 151 insertions(+), 36 deletions(-) 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 &GAP; 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