71

Mathematica does have a nice package manager. Packages are called paclets, and they can be managed using the functions from the PacletManager` context.

How can I package up my own packages as paclets, and manage their installation?

Related:

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263

3 Answers3

54

The following answer is not complete, but does give one possible solution. There's a lot more to learn about the paclet manager, so please contribute another answer if you can, or correct this answer if you find any mistakes.


I originally posted this on Wolfram Community, following a nice tutorial by Emerson Willard on how to create paclets using Workbench. Most of the information is derived from studying GitLink.


To use Paclet Manager functions, it may be necessary to evaluate Needs["PacletManager`"] first.


Introduction

Packages can be bundled into .paclet files, which are easy to distribute and install.

.paclet files appear to be simply zip files that can contain a Mathematica package or other extensions to Mathematica, along with some metadata in a PacletInfo.m. The metadata makes it possible to manage installation, uninstallation and updating automatically.

I'm going to illustrate this using MaTeX. It is my smallest published package, so I used it for experimentation.

How to add the required metadata?

First make sure that your package is following the standard directory structure.

Then create a PacletInfo.m file in the package root with a minimum amount of metadata. Make sure that Name and Version are present. For MaTeX I could start e.g. with this:

Paclet[
    Name -> "MaTeX",
    Version -> "1.6.2",
    MathematicaVersion -> "10.0+",
    Description -> "Create LaTeX-typeset labels within Mathematica.",
    Creator -> "Szabolcs Horvát"
]

This is sufficient to make it possible to pack and install a paclet. But it is not sufficient for making it loadable with Needs. For that we need to add the "Kernel" extension:

Paclet[
    Name -> "MaTeX",
    Version -> "1.6.2",
    MathematicaVersion -> "10.0+",
    Description -> "Create LaTeX-typeset labels within Mathematica.",
    Creator -> "Szabolcs Horvát",
    Extensions -> 
        {
            {"Kernel", Root -> ".", Context -> "MaTeX`"}
        }
]

The two critical arguments to the `"Kernel"`` extension are:

  • Context sets the context of the package. Whatever you put here will be recognized by Needs and FindFile, but ideally it should also be compatible with the package name and the standard file name resolution.

  • Root sets the application root. FindFile seems to resolve the context to a path through this, but also following the standard rules.

Of course you can also add the "Documentation" extension to integrate with the Documentation Centre, but that is not required for the functionality I describe here.

Much more detailed information on PacletInfo files is here:

How to bundle a package into a .paclet file?

Simple use the PackPaclet function on the application directory. It will use the information from PacletInfo.m. It is a good idea to remove any junk files and hidden files to avoid them from getting packed up.

Warning: Before doing this, make a copy of the application directory. Don't accidentally delete any files used by your version control system.

After making a copy of the package directory, in my case called MaTeX, I did this:

Make sure we're in the parent directory of the application directory:

In[2]:= FileNames[]
Out[2]= {".DS_Store", "MaTeX"}

Delete any junk files like ``.DS_Store` (which macOS likes to create):

In[4]:= DeleteFile /@ FileNames[".*", "MaTeX", Infinity]

Create .paclet file:

In[5]:= PackPaclet["MaTeX"]
Out[5]= "/Users/szhorvat/Desktop/pacletbuild/MaTeX-1.6.2.paclet"

Install it permanently:

In[6]:= PacletInstall[%]
Out[6]= Paclet[MaTeX, 1.6.2, <>] 

Multiple versions may be installed at the same time. Finds all installed versions using:

In[7]:= PacletFind["MaTeX"]
Out[7]= {Paclet[MaTeX, 1.6.2, <>]}

While this Paclet expression is formatted concisely, it contains all the metadata from PacletInfo.m, plus its installed location. You can see all this by applying InputForm to it.

FindFile (and therefore also Needs) will always resolve to the latest version:

In[8]:= FindFile["MaTeX`"]
Out[8]= "/Users/szhorvat/Library/Mathematica/Paclets/Repository/MaTeX-1.6.2/Kernel/init.m"

PacletFind will return the highest version first. To uninstall all but the highest version, we can use something like PacletUninstall /@ Rest@PacletFind["MaTeX"]. To uninstall all versions at once,

PacletUninstall["MaTeX"]

How to work with paclets during development?

During development we don't want to pack the paclet and install it after every change. It is much more convenient to be able to load it directly from the development directory.

I can do this by adding the parent directory of the application directory (i.e. MaTeX in the above example) as a paclet directory. Since I keep the development version of MaTeX in ~/Repos/MaTeX-wb/MaTeX, I can simply use

PacletDirectoryAdd["~/Repos/MaTeX-wb"]

After this Needs["MaTeX`"] will load the dev version.


As a proof of concept and an experiment, I started distributing MaTeX in this format. You can use it as a reference in addition to GitLink.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 2
    +1. A couple of complementary comments here: first, if you change something in the paclet and want the new one to be used, make sure to change (increment) the paclet version. AFAIR, patch version number change (in the terminology of semantic versioning) should be enough. Second, make sure that your Mathematica version number is small enough to cover the earliest M version with which you intend the package to work, because otherwise the paclet manager won't consider the paclet valid for versions earlier than the one indicated. – Leonid Shifrin Nov 14 '16 at 14:40
  • @Leonid I know that Needs will load the latest version. Do you know if PacletFind will return that same version first in the list? This is what happens when I try it, but I do not know if this is accidental or guaranteed. – Szabolcs Nov 14 '16 at 18:31
  • It is guaranteed. You can always check by using PacletInformation[pacletName], which version will be found and loaded at any given time. – Leonid Shifrin Nov 14 '16 at 18:49
  • Could you add a bit on the distribution aspect? That is, if I put a package in a paclet, how would users get and install it? Thanks! – Chris K Nov 14 '16 at 20:23
  • 2
    @ChrisK They download the .paclet file (it's just one file) and install it using the PacletInstall command as above. They can find all installed versions using PacletFind or uninstall them using PacletUninstall. – Szabolcs Nov 14 '16 at 21:33
  • 1
    @Szabolcs Can you give PacletInstall a web address to a file on github? Installing without having to download first is convenient – Jason B. Nov 16 '16 at 02:17
  • 1
    @JasonB I just tried it. Yes, it works, but only with http, not with https. That pretty much rules out GitHub. This is what Emerson Willard said as well on Wolfram Community. – Szabolcs Nov 16 '16 at 08:13
  • 1
    great question and answer. One question I still have (don't think it qualifies for an extra question): can one load one of the older installed versions? Otherwise are they really only there for fallback purposes when the newest one is uninstalled? – Albert Retey Nov 30 '16 at 23:42
  • @AlbertRetey I don't know and given the time it takes to spelunk the answers, I think it's worth asking as a separate question. – Szabolcs Dec 01 '16 at 07:14
  • @Szabolcs does PackPaclet do something more than git archive (with specific parameters)? I'd like to pack it on server side but there is no Kernel to run PackPaclet. – Kuba Feb 02 '17 at 08:32
  • @Kuba I don't think so. It probably does extra checks. Also it produces a zip file with a supported compression method (deflate), which is what most, but not all, compressors do. – Szabolcs Feb 02 '17 at 08:36
  • @Szabolcs I can pack it locally to be sure it is fine and then send source to the server. Git archive deflate unless -o is used (I'm not an expert though). – Kuba Feb 02 '17 at 08:38
  • @Kuba git archive should produce a compatible zip, I think. I don't think compression type would be a problem in the vast majority of cases. I just mentioned this because OS X's builtin unarchiver would not uncompress a paclet file that is renamed to have the zip extension, and I don't know why. Command line zip handles it just fine. – Szabolcs Feb 02 '17 at 08:41
  • @Kuba Be careful with git archive because it only exports things that are already committed. Stuff that's modified by not committed won't be exported. – Szabolcs Feb 02 '17 at 08:42
  • @Szabolcs let's continue on chat – Kuba Feb 02 '17 at 08:42
25

Szabolcs answer shows how we can build a .paclet, but the PacletManager also contains the possibility to serve packages from an own site. As I realized after writing this, most of the information here can also be found in this Wolfram Community post, a link contained in Szabolcs PacletInfo.m documentation project.

Paclet Server Setup

The easiest setup is to use some webspace which serves static content. On that put a PacletSite.mz file into the root directory which contains information about which paclets and versions that site will serve. Add a directory named Paclets and put the paclets you did build as described by Szabolcs into that. The content of PacletSite.mz needs to be as follows:

pacletsite = PacletSite[Paclet[
  "Name" -> "PckgName", 
  "Version" -> "1.0.0", 
  "MathematicaVersion" -> "9.0+", 
  "Description" -> "A package to try the PacletSite functionality.", 
  "Creator" -> "your name",
  "Extensions" -> {{"Kernel", Root -> ".", Context ->"TryPacletSite`"}}
  ],
  ...

]

that is an expression with a Head PacletSite and as arguments a Sequence of Paclet expressions, which are basically the same as what is in a PacletInfo.m file, although I think you will need strings as labels here whereas the PacletInfo.m wants symbols, or at least some of the (java?) functionality that uses it like PackPaclet.

The PacletSite.mz can be generated from the above expression with:

Export["PacletSite.mz",{pacletsite},{"ZIP", {{"PacletSite.m", "Package"}}}]

Upload that and the paclet files to the server and test whether you can download them e.g. by visiting (of course you'll need to fill your own urls) "http://your.pacletsite.url/PacletSite.mz" and "http://your.pacletsite.url/Paclets/PckgName-1.0.0.paclet"

If that works you are set to experiment with the paclet manager.

Client Side Usage of PacletSite

this will show the currently configured paclet-sites, which should be just the wolfram research ones:

PacletSites[]

this will add your own paclet site url (for experimenting, I prefered to prepend it):

PacletSiteAdd["http://your.pacletsite.url","Description", Prepend -> True]

Note that PacletSiteAdd will add that url permanently, that means it will persist in the next session, you will need to use PacletSiteRemove to get rid of it.

The following will get the information about which paclets the given site serves, that is it will download and read the content of your PacletSite.mz:

PacletSiteUpdate["http://your.pacletsite.url"]

now it is possible to install a package from that site (optionally as shown using a specific version):

PacletInstall[{"PckgName", "1.0.0"}]

one installed, you can list all installed versions of a package:

PacletFind["PckgName"]

and of course load it:

Get["PckgName`"]

if you now put a newer version onto the server and also update the information in PacletSite.mz you can do:

PacletCheckUpdate["PckgName", "UpdateSites" -> True]

which will return a list of paclets for which the site now has newer versions than what you have installed. Using:

PacletUpdate["PckgName"]

will actually install the newest available version (if it is compatible with your Mathematica version). You should now see that in the list of installed versions and when loading you should get the new version:

PacletFind["PckgName"]

Get["PckgName`"]

to uninstall (all versions), you would do:

PacletUninstall["PckgName"]

check that all versions are gone:

PacletFind["PckgName"]

finally, to get rid of the added paclet site you would need to do:

PacletSiteRemove["http://your.pacletsite.url"]

I have no experience with how good this works in practice, I just did set this up and tried and it seems to work with version 9, 10 and 11. There seem to be some timeouts involved so you might get bad results if the server is too slow. If anyone makes own experiments I am very interested to hear how good that works for them. Of course all that functionality is undocumented with all the consequences that has. On the other hand it is the mechanism that WRI seems to use since at least version 9 to provide their own paclets so I would expect it should be fit for production...

Important: Security Considerations

As Szabolcs and Sjoerd C. de Vries have mentioned in their comments of course installing from an unkown web source has security issues. So when installing from external sources always be careful and act with a decent measure of mistrust.

The described setup does not actually add additional insecurities (you can already download and install Mathematica code from websources in other ways), but it of course makes it somewhat easier to get trapped into running malicous code.

The whole mechanism doesn't have any security measures and I don't see an easy way to provide one. To my understanding (I'm not a security expert), when you add a paclet site url as described you are trusting (at least):

  1. the package author to not provide malicious code,
  2. the web server which serves the paclets to actually serve the authors versions of these packages and
  3. your own computer and the DNS servers you are using that they correctly resolve the paclet-server address and not redirect to a malicous server.

What package managers for other languages or OS distributions usually do is to provide a certification mechanisms which make it much harder for malicious code to sneak in without the package provider realizing. AFAIK such a mechanism can prohibit attacks to 2. and 3.

Of course even with such a certification mechanism you still would trust the maintainer of the packages that their code won't do anything bad (I think there is no way to solve 1. technically)...

Albert Retey
  • 23,585
  • 60
  • 104
  • It seems to work well: http://szhorvat.net/paclets/ Interestingly http://pacletserver.wolfram.com/PacletInfo.mz just redirects to wolfram.com. PacletInstall["PckgName"] seems to work without PacletSiteUpdate too. Do you know if the system ever auto-updates third party paclets? – Szabolcs Dec 03 '16 at 11:57
  • @Szabolcs: the Wolfram servers obviously don't simply deliver static content but do at least some checks of http headers, presumably to only let the correct versions of Mathematica access the paclet data. The headers that the PacletManager sends contain a paclet manager version number so they probably even could change the server side implementation for a newer paclet manager version and then redirect accordingly. If interested you'll find the needed headers in the function definitions within the PacletManager package... – Albert Retey Dec 03 '16 at 15:04
  • @Szabolcs: will you keep that site up and running? If so would you mind if I adjust the site and package definitions in my answer accordingly? I think it would be instructive if the code would be working for someone interested without the need to set up a server. My own test setup unfortunately isn't permanent... – Albert Retey Dec 03 '16 at 15:08
  • Feel free to use it as an example. I hope that I could keep it up and running, but it's hard to know how it will turn out. If I do need to take it down in the future, I'll edit it out of the answer. – Szabolcs Dec 03 '16 at 17:34
  • 1
    @szabolcs It would be good to think about safety issues of paclets served from (relatively) unknown sites, given the ability of Mathematica to erase a user's entire hard disk (think about FileSystemMap and DeleteFile). – Sjoerd C. de Vries Dec 04 '16 at 11:12
  • 2
    @SjoerdC.deVries Yes, that's what I wrote in my original comment that now I deleted. But in the end I'm not sure that all this (paclet servers) make the situation much worse. Someone could post a malicious Uncompress[...] or Get["http://..."] or Import[...] here on SE and many people would fall prey to it. Or even just a notebook. Mathematica's security handling is bad. You don't have to click "Enable Dynamics", it's enough to just evaluate anything in the notebook and dynamics get enabled right away. – Szabolcs Dec 04 '16 at 12:13
  • @Sjoerd Now what could go wrong with a paclet server? I could post malicious paclets, so if you use it you have to trust me. That's no different from downloading from my GitHub. Someone could hack my server and post malicious paclets. But someone could also get my GitHub password, and post a bad paclet there. It's true that with the paclet server the risk is a bit worse because the updates are so much easier. But it isn't very different. You are right that this is something to at least think about and what I wrote in my original comment was that this may one of the reasons ... – Szabolcs Dec 04 '16 at 12:16
  • 1
    ... why I might not be able to keep the server going. Another reason is that the functionality isn't really targeted at end users at this point and it's easy to mess up stuff (e.g. remove the WRI paclet server by accident). You wouldn't believe some of the support emails I got about MaTeX. If there's even the slightest chance to break something, someone will definitely break it. – Szabolcs Dec 04 '16 at 12:18
  • @Szabolcs The main problem of paclets would be the easy of installing it. It would prevent people from taking a glance at the code. Of course, there's no guarantee that they would spot something malicious otherwise. – Sjoerd C. de Vries Dec 04 '16 at 12:50
  • @SjoerdC.deVries: I think your discussion has a point, I was hesitating to change the code and insert szabolcs url just for gut feelings, but now I think it is better to leave it like it is. Everything else would take extra burdon on Szabolcs to keep his server running AND keep it save. As for security I think Szabolcs is right, I don't think this does add any additional insecurities and I doubt whether people do look at the code if installation is more complicated. They probably just won't install or not succeed in doing so, which might prevent some attacks. – Albert Retey Dec 04 '16 at 14:00
  • 2
    @AlbertRetey you can more easily compile a PacletSite.mz using PacletManager`Package`BuildPacletSiteFiles. It operates on the top-level PacletServer directory. – b3m2a1 Sep 01 '17 at 18:25
  • @b3m2a1: interesting, there seem to be more useful functions in that context and its definition. But as it is an undocumented function I think it would only make sense to add it to my answer with some kind of documentation how it works and what it does (which I don't know, do you?). Except for being somewhat shorter, what other advantage would it have compared to the Export that I have mentioned? – Albert Retey Sep 02 '17 at 10:22
  • Mostly I think the use is just that it automatically loops over the paclets in the Paclets directory. Similarly I think it's useful in that it's built into the manager, so if something about the paclet format changes, it will still work. – b3m2a1 Sep 02 '17 at 12:55
  • I am not sure what {"ZIP", {"PacletSite.m", "Package"}} means and I would like to because export $Failed :( – Kuba Jan 07 '19 at 11:51
  • @Kuba: you will need double braces as in my example, it is the syntax as documented for export to "ZIP" format, you can give a list of contents and export each entry to an extra file where you give a filename and the format of the file. The error was that I have been missing the braces for the content list, which I now have added in the answer. I think the current version of the Export should work... – Albert Retey Jan 07 '19 at 17:56
9

If you want to extend Albert Retey's answer to just use Wolfram tech you can set up your server in the cloud. I just set this up for myself as a proof-of-concept and it seems to work just fine.

Step 1: The Cloud

Get a free cloud account that you can put these into. Obviously restrictions will apply to the size of the packages you can distribute and whatnot, but looking at the pricing page you get .2 GB of space which, if you're mostly moving code base, not data, should suffice. If not see this answer to see how we can set this up using Google Drive too. The basic trick is to put up your paclet but provide an HTTPRedirect to a Google Drive download link.

Step 2: Deployment

Generate your application(s) that you want to move. I took some stuff I've developed, including the package that has the code that pushes to a cloud paclet, in an application I called AppSampler.

Configure it like you were gonna push to, say, GitHub, but now instead we're gonna push to the cloud.

(Note that if you just want to put your paclet up so that it's installable by PacletInstall you can just upload that--no need for this paclet site)

First we do our PacletSite.mz file:

co = CloudDeploy[None, "AppSampler/PacletSite.mz"];
CopyFile[ ".../AppSampler/PacletSite.mz", co];
SetPermissions[co, "Public"]

It's really just that last step that's in any way important, as it allows the paclet manager to access it. If I remember correctly from testing "Private" also works if you're on your own cloud account. Alternatively you can share with a group of people by setting up a PermissionsGroup.

Then we do the same for the paclets. In my case I just have the one, but you could do more:

co = CloudDeploy[None, "AppSampler/Paclets/AppSampler-0.0.paclet"];
CopyFile[ ".../AppSampler/Paclets/AppSampler-0.0.paclet", co];
SetPermissions[co, "Public"]

Step 3: Installation

Then after removing every trace of the paclet from out computers we do a PacletSiteAdd on the cloud repository, which, in this case lives at:

"http://www.wolframcloud.com/objects/user-affd7b1c-ecb6-4ccc-8cc4-4d107e2bf04a/_paclets/AppSampler/"

And then PacletInstall@"AppSampler" will pull in the paclet.

The great part of this is that it is a) free until WRI decides otherwise and (critically) b) possible to do entirely from Mathematica without having to link to any external resources.

There's something on the pricing page about a 30-Day limit on deployments, which is maybe applicable here -- does CloudDeploy count if it's really just to make the file exist for CopyFile? If it does that's a draw back, but probably non-fatal and at the very least this is still super convenient for temporary deployments. These paclets seem to survive in perpetuity.

b3m2a1
  • 46,870
  • 3
  • 92
  • 239