I second the suggestion of Szabolcs, in that paclets provide a clean declarative way of WL version - aware distribution of your package. You can have multiple paclets for the same package, with different paclet version and minimal WL version indicated in their PacletInfo
file, and paclet manager will automatically pick the paclet with maximal paclet version, compatible with WL version requirements, at installation time.
OTOH, using Abort[]
doesn't look like a good solution in this case, since it is too disruptive.
Should you insist on your suggested scheme, I can offer some solution, which should work when the package is loaded with Get
or Needs
from a file. It will not work when the package is evaluated interactively in the FrontEnd, however.
Here is a tiny package AbortPackageLoad.m
, which you can place next to your own package, or anywhere else on $Path
:
BeginPackage["AbortPackageLoad`"]
AbortPackageLoad::usage =
"AbortPackageLoad[context, metadata] attempts to gracefully abort loading \
of a package from where it is called. The second optional argument may \
contain the metadata association, describing the specific reason for abort \
and associated information."
AbortPackageLoad::genfail =
"Loading of package `1` failed for certain internal reason";
AbortPackageLoad::invldver =
"Loading of package `1` failed. You need version `2`";
Begin["`Private`"]
$reasonMessageMap = <|
"general" -> Function[Message[AbortPackageLoad::genfail]]
,
"invalid_version" -> Function[
{context, meta},
Message[AbortPackageLoad::invldver, context, meta["RequiredVersion"]]
]
|>
removeContext[cont_String] /; MemberQ[Contexts[], cont] :=
Quiet @ Remove[Evaluate[cont <> "*"]]
AbortPackageLoad[context_][metadata_ : <|"FailureReason" -> "general"|>] :=
With[
{private = context <> "Private`"}
,
$reasonMessageMap[Lookup[metadata, "FailureReason", "general"]][
context, metadata
];
If[$InputFileName =!= "",
Scan[removeContext, {context, private}];
Unprotect[$Packages];
$Packages = DeleteCases[$Packages, context];
Protect[$Packages];
If[$Context === private, End[]];
If[$Context === context, EndPackage[]];
$ContextPath = DeleteCases[$ContextPath, context];
Return[$Failed, Get]
];
]
End[]
EndPackage[]
Here is one way to use it in your package:
BeginPackage["Futuristic`"]
amazingFeature::usage = "Whoa, dude.
The functionality of this amazingFeature requires the latest \
Mathematica";
`Private`abort = AbortPackageLoad`AbortPackageLoad[$Context];
Begin["`Private`"]
Needs["AbortPackageLoad`"]
lowestAllowedVersionNumber = 14;
If[$VersionNumber < lowestAllowedVersionNumber,
abort[<|
"FailureReason" -> "invalid_version",
"RequiredVersion" -> "14+"
|>]
];
amazingFeature[x_] := 2 x
End[]
EndPackage[]
EDIT
In response to the suggestion by Szabolcs in comments, I have modified the AbortPackageLoad
function, so that it can also be used at the very top of the package file, before BeginPackage[]
statement - if that's desirable / preferable. Or even inside the package, after BeginPackage[]
but before Begin["`Private`"]
. This is how it could be used in this fashion:
Block[{$ContextPath},
Needs["AbortPackageLoad`"];
If[$VersionNumber < 14,
AbortPackageLoad`AbortPackageLoad["Futuristic`"][<|
"FailureReason" -> "invalid_version",
"RequiredVersion" -> "14+"
|>]
];
]
BeginPackage["Futuristic`"]
...
EndPackage[]
END EDIT
The way AbortPackageLoad
works is to clean and remove the public and private contexts of the package being loaded, then clean up global variables $Package
and $ContextPath
, ensure that package contexts are properly closed (so that $Context
will point back to whatever context was there before loading your package), and finally, crucially, uses two-argument Return
to return from Get
early. Since Needs
calls Get
internally, this will work for Needs
too.
When package is loaded from file, this mechanism works, because the file is parsed line by line, and complete expressions further in the file are not sent to evaluation early. When you execute the package from a FrontEnd cell, this will not work, because expressions further in the cell will still be parsed and send for evaluation. There are some ways to cancel the rest of evaluation queue, but I wasn't convinced that they are robust enough for this use case, so I excluded FrontEnd evaluation use case with the check If[$InputFileName =!= "", ...]
.
I don't see obvious cases where the above solution will break for non-MX packages loaded with Get
/ Needs
, but OTOH I would still hesitate to use it in production code.
$VersionNumber
and abort if it's too low. $\endgroup$WolframVersion
, butMathematicaVersion
will be recognized by even very old Mathematica (including version 9), which is why I prefer it. Example of a more rudimentary check: github.com/szhorvat/IGraphM/blob/master/IGraphM/Kernel/… $\endgroup$$CloudEvaluation
. I didn't know aboutAbort[]
. Then I can doCheck[BeginPackage["Futuristic``","FancyNewv14Context``"],EndPackage[];Print["Message :("]; Abort[]];
. But perhaps now is a good time to learn the whole paclet business... $\endgroup$