Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing a path to GAP.Packages.load #893

Merged
merged 3 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 110 additions & 36 deletions src/packages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
module Packages

using Downloads
import ...GAP: Globals, GapObj, sysinfo
import ...GAP: Globals, GapObj, RNamObj, sysinfo, Wrappers

const DEFAULT_PKGDIR = Ref{String}()

Expand All @@ -26,7 +26,7 @@ function init_packagemanager()
return GapObj(Dict{Symbol, Any}(:success => false), recursive=true)
end
end
Globals.MakeReadOnlyGlobal(GapObj("PKGMAN_DownloadURL"))
Wrappers.MakeReadOnlyGlobal(GapObj("PKGMAN_DownloadURL"))

# Install a method (based on Julia's Downloads package) as the first choice
# for the `Download` function from GAP's utils package,
Expand Down Expand Up @@ -57,23 +57,26 @@ function init_packagemanager()

# put the new method in the first position
meths = Globals.Download_Methods
Globals.Add(meths, GapObj(r, recursive=true), 1)
Wrappers.Add(meths, GapObj(r, recursive=true), 1)
end
end

"""
load(spec::String, version::String = ""; install::Bool = false, quiet::Bool = true)

Try to load the GAP package with name `spec`.
Try to load the GAP package given by `spec`, which can be either the name
of the package or a local path where the package is installed
(a directory that contains the package's `PackageInfo.g` file).

If `version` is specified then try to load a version of the package
that is compatible with `version`, in the sense of
[GAP's CompareVersionNumbers function](GAP_ref(ref:CompareVersionNumbers)),
otherwise try to load the newest installed version.
Return `true` if this is successful, and `false` otherwise.

If `install` is set to `true` and (the desired version of) the required
GAP package is not yet installed then [`install`](@ref) is called first,
in order to install the package;
GAP package is not yet installed and `spec` is the package name
then [`install`](@ref) is called first, in order to install the package;
if no version is prescribed then the newest released version of the package
will be installed.

Expand All @@ -82,27 +85,98 @@ If `quiet` is set to `false` then package banners are shown for all packages
being loaded. It is also passed on to [`install`](@ref).
"""
function load(spec::String, version::String = ""; install::Bool = false, quiet::Bool = true)
# Try to load the package.
# Decide whether `spec` is a path to a directory that contains
# a `PackageInfo.g` file.
package_info = joinpath(spec, "PackageInfo.g")
spec_is_path = isdir(spec) && isfile(package_info)

# If `spec` contains a slash then it is not a package name.
'/' in spec && ! spec_is_path && return false

# The interpretation of `spec` as a package name has precedence
# over the interpretation as a path.
# Try to load the package, assuming that `spec` is its name.
gspec = GapObj(spec)
gversion = GapObj(version)
loaded = Globals.LoadPackage(gspec, gversion, !quiet)
if loaded == true
return true
elseif Globals.IsPackageLoaded(gspec)
# Another version is already loaded.
# Perhaps we could install the required version,
# but then we would not be able to load it into the current session
# and thus in any case `false` must be returned.
# It would be a strange side effect if the required version
# would afterwards be loadable in a fresh Julia session,
# thus we do not try to install the package here.
return false
elseif install == true
if spec_is_path
# If there is no package `gspec` and if the info level of
# `GAP.Globals.InfoWarning` is at least 1 then GAP prints a warning.
# Avoid this warning.
warning_level_orig = Wrappers.InfoLevel(Globals.InfoWarning)
Wrappers.SetInfoLevel(Globals.InfoWarning, 0)
end
loaded = Wrappers.LoadPackage(gspec, gversion, !quiet)
if spec_is_path
Wrappers.SetInfoLevel(Globals.InfoWarning, warning_level_orig)
end

loaded == true && return true

if Wrappers.IsPackageLoaded(gspec)
# Another version is already loaded.
# Perhaps we could install the required version,
# but then we would not be able to load it into the current session
# and thus in any case `false` must be returned.
# It would be a strange side effect if the required version
# would afterwards be loadable in a fresh Julia session,
# thus we do not try to install the package here.
return false
end

if spec_is_path
# Assume that the package is installed in the given path.
# In order to call `GAP.Globals.SetPackagePath`,
# we have to determine the package name.
# (`Wrappers.SetPackagePath` does the same,
# but it needs the package name as an argument.)
gap_info = Globals.GAPInfo::GapObj
Wrappers.UNB_REC(gap_info, RNamObj("PackageInfoCurrent"))
Wrappers.Read(GapObj(package_info))
record = gap_info.PackageInfoCurrent
Wrappers.UNB_REC(gap_info, RNamObj("PackageInfoCurrent"))
pkgname = Wrappers.NormalizedWhitespace(Wrappers.LowercaseString(
record.PackageName))
rnam_pkgname = Wrappers.RNamObj(pkgname)

# If the package with name `pkgname` is already loaded then check
# whether the installation path is equal to `spec`.
# (Note that `Wrappers.SetPackagePath` throws an error if a different
# version of the package is already loaded.)
if Wrappers.IsPackageLoaded(pkgname) &&
Wrappers.ISB_REC(gap_info.PackagesLoaded, rnam_pkgname)
install_path = Wrappers.ELM_REC(gap_info.PackagesLoaded, rnam_pkgname)[1]
return joinpath(string(install_path), "PackageInfo.g") == package_info
end
#TODO: What shall happen when `spec` is a symbolic link that points to
# the installation path of the loaded package?

# First save the available records for the package in question, ...
old_records = nothing
if Wrappers.ISB_REC(gap_info.PackagesInfo, rnam_pkgname)
old_records = Wrappers.ELM_REC(gap_info.PackagesInfo, rnam_pkgname)
end

# ... then try to load the package, ...
Wrappers.SetPackagePath(pkgname, gspec)
loaded = Wrappers.LoadPackage(pkgname, gversion, !quiet)

# ..., and reinstall the old info records
# (which were removed by `Wrappers.SetPackagePath`).
if old_records isa GapObj
Wrappers.Append(Wrappers.ELM_REC(gap_info.PackagesInfo, rnam_pkgname),
old_records)
end

loaded == true && return true
Wrappers.IsPackageLoaded(gspec) && return false
end

if install == true
# Try to install the given version of the package,
# without showing messages.
if Packages.install(spec, version; interactive = false, quiet)
# Make sure that the installed version is admissible.
return Globals.LoadPackage(gspec, gversion, !quiet) == true
return Wrappers.LoadPackage(gspec, gversion, !quiet) == true
end
end

Expand Down Expand Up @@ -147,16 +221,16 @@ function install(spec::String, version::String = "";
mkpath(pkgdir)

if quiet
oldlevel = Globals.InfoLevel(Globals.InfoPackageManager)
Globals.SetInfoLevel(Globals.InfoPackageManager, 0)
oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0)
end
if version == ""
res = Globals.InstallPackage(GapObj(spec), interactive)
res = Wrappers.InstallPackage(GapObj(spec), interactive)
else
res = Globals.InstallPackage(GapObj(spec), GapObj(version), interactive)
res = Wrappers.InstallPackage(GapObj(spec), GapObj(version), interactive)
end
if quiet
Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
end
return res
end
Expand Down Expand Up @@ -187,13 +261,13 @@ function update(spec::String; interactive::Bool = true, quiet::Bool = false,
mkpath(pkgdir)

if quiet
oldlevel = Globals.InfoLevel(Globals.InfoPackageManager)
Globals.SetInfoLevel(Globals.InfoPackageManager, 0)
res = Globals.UpdatePackage(GapObj(spec), interactive)
Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0)
res = Wrappers.UpdatePackage(GapObj(spec), interactive)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
return res
else
return Globals.UpdatePackage(GapObj(spec), interactive)
return Wrappers.UpdatePackage(GapObj(spec), interactive)
end
end
# note that the updated version cannot be used in the current GAP session,
Expand Down Expand Up @@ -222,13 +296,13 @@ function remove(spec::String; interactive::Bool = true, quiet::Bool = false,
mkpath(pkgdir)

if quiet
oldlevel = Globals.InfoLevel(Globals.InfoPackageManager)
Globals.SetInfoLevel(Globals.InfoPackageManager, 0)
res = Globals.RemovePackage(GapObj(spec), interactive)
Globals.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
oldlevel = Wrappers.InfoLevel(Globals.InfoPackageManager)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, 0)
res = Wrappers.RemovePackage(GapObj(spec), interactive)
Wrappers.SetInfoLevel(Globals.InfoPackageManager, oldlevel)
return res
else
return Globals.RemovePackage(GapObj(spec), interactive)
return Wrappers.RemovePackage(GapObj(spec), interactive)
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const Obj = Union{GapObj,FFE,Int64,Bool}
"""
GapInt

Any GAP integer object is represened in Julia as either a `GapObj` (if it
Any GAP integer object is represented in Julia as either a `GapObj` (if it
is a "large" integer) or as an `Int` (if it is a "small" integer). This
type union can be used to express this conveniently, e.g. when one wants to
help type stability.
Expand Down
16 changes: 16 additions & 0 deletions src/wrappers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module Wrappers
using GAP
import GAP: @wrap

@wrap Add(x::GapObj, y::GapObj, z::Int)::Nothing
@wrap AdditiveInverseSameMutability(x::Any)::Any
@wrap Append(x::GapObj, y::GapObj)::Nothing
@wrap ASS_LIST(x::Any, i::Int, v::Any)::Nothing
@wrap ASS_MAT(x::Any, i::Int, j::Int, v::Any)::Nothing
@wrap ASS_REC(x::Any, y::Int, v::Any)::Nothing
Expand All @@ -20,7 +22,10 @@ import GAP: @wrap
@wrap ELMS_LIST(x::Any, y::Any)::Any
@wrap EQ(x::Any, y::Any)::Bool
@wrap IN(x::Any, y::Any)::Bool
@wrap InfoLevel(x::GapObj)::Int
@wrap INT_CHAR(x::Any)::Int
@wrap InstallPackage(x::GapObj, y::Bool)::Bool
@wrap InstallPackage(x::GapObj, y::GapObj, z::Bool)::Bool
@wrap InverseSameMutability(x::Any)::Any
@wrap IS_JULIA_FUNC(x::Any)::Bool
@wrap ISB_LIST(x::Any, i::Int)::Bool
Expand All @@ -33,6 +38,7 @@ import GAP: @wrap
@wrap IsEmpty(x::Any)::Bool
@wrap IsList(x::Any)::Bool
@wrap IsMatrixObj(x::Any)::Bool
@wrap IsPackageLoaded(x::GapObj)::Bool
@wrap IsRange(x::Any)::Bool
@wrap IsRangeRep(x::Any)::Bool
@wrap IsRecord(x::Any)::Bool
Expand All @@ -42,10 +48,14 @@ import GAP: @wrap
@wrap IsVectorObj(x::Any)::Bool
@wrap Iterator(x::Any)::Any
@wrap Length(x::Any)::GapInt
@wrap LoadPackage(x::GapObj, y::GapObj, z::Bool)::Any
@wrap LowercaseString(x::GapObj)::GapObj
@wrap LQUO(x::Any, y::Any)::Any
@wrap LT(x::Any, y::Any)::Bool
@wrap MakeReadOnlyGlobal(x::Any)::Nothing
@wrap MOD(x::Any, y::Any)::Any
@wrap NextIterator(x::Any)::Any
@wrap NormalizedWhitespace(x::GapObj)::GapObj
@wrap NumberColumns(x::Any)::GapInt
@wrap NumberRows(x::Any)::GapInt
@wrap NumeratorRat(x::Any)::GapInt
Expand All @@ -55,14 +65,20 @@ import GAP: @wrap
@wrap PROD(x::Any, y::Any)::Any
@wrap PushOptions(x::Any)::Nothing
@wrap QUO(x::Any, y::Any)::Any
@wrap Read(x::GapObj)::Nothing
@wrap RecNames(x::Any)::Any
@wrap RemovePackage(x::GapObj, y::Bool)::Bool
@wrap RNamObj(x::Any)::Int
@wrap SetInfoLevel(x::GapObj, y::Int)::Nothing
@wrap SetPackagePath(x::GapObj, y::GapObj)::Nothing
@wrap ShallowCopy(x::Any)::Any
@wrap String(x::Any)::Any
@wrap StringDisplayObj(x::Any)::Any
@wrap StringViewObj(x::Any)::Any
@wrap StructuralCopy(x::Any)::Any
@wrap SUM(x::Any, y::Any)::Any
@wrap UNB_REC(x::GapObj, y::Int)::Nothing
@wrap UpdatePackage(x::GapObj, y::Bool)::Bool
@wrap ZeroSameMutability(x::Any)::Any

end
17 changes: 17 additions & 0 deletions test/packages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,21 @@
# @test GAP.Packages.install("fga", interactive = false, pkgdir = pkgdir)
# @test GAP.Packages.remove("fga", interactive = false, pkgdir = pkgdir)

# Load packages via their local paths.
# - a package that was already loaded, with the same path
path = string(GAP.Globals.GAPInfo.PackagesLoaded.ctbllib[1])
@test GAP.Packages.load(path)

# - a package that was already loaded, with another installation path
#TODO: How to guarantee two installed versions with different paths?

# - a package that was not yet loaded (only once in a Julia session)
if ! GAP.Globals.IsPackageLoaded(GAP.GapObj("autodoc"))
path = string(GAP.Globals.GAPInfo.PackagesInfo.autodoc[1].InstallationPath)
@test GAP.Packages.load(path)
end

# - a nonexisting path
path = path * "xxx"
@test ! GAP.Packages.load(path)
end