############# EUPS Tutorial ############# `EUPS`_\—"Extended Unix Product System"—is a tool for managing multiple versions of interdependent software packages. The LSST stack consists of many separate packages: ``lsst_apps`` alone depends on more than 50; other top level packages bring in more. The relationships between these packages can be complex ("``lsst_apps`` version ``X`` depends on ``afw`` version ``Y`` which depends on ``boost`` version at least ``Z`` but no greater than ``Z+N``"). Furthermore, it's convenient to keep multiple versions of the packages installed and available simultaneously—the developer likely wants to develop targeting today's version of the stack, while fixing bugs in yesterday's; the scientist to run the latest versions of the algorithms while also being able to reproduce their results from last year. EUPS aims to make this situation tractable. Here, we provide a tutorial-based introduction to basic EUPS functionality. Throughout, we use the ``%`` symbol to represent a shell prompt (i.e., this preceeds commands one might type into a terminal). .. _EUPS: https://github.com/RobertLuptonTheGood/eups Getting Started =============== Having installed the stack, you already have EUPS available. However, its internal database is pre-populated with knowledge about the stack packages you have installed. For simplicity, we begin by re-targeting it by setting the ``EUPS_PATH`` variable:: % . ${STACK_PATH}/loadLSST.bash % eups path ${STACK_PATH} % unsetup lsst % export EUPS_PATH="/tmp/eups_demo" % mkdir -p ${EUPS_PATH}/ups_db % eups path /tmp/eups_demo Observe that the majority of EUPS commands follow pattern :command:`eups ` (reminiscent of Git, for example). :command:`(un)setup` is the exception. Building and Using a Simple EUPS Product ======================================== Assume we have a trivial software package — ``a`` — which we want to have EUPS manage as a "product". Our package is about as simple as they come:: % cat python/a.py __VERSION__ = 1 % cat bin/a #!/usr/bin/env python import a if __name__ == "__main__": print("Package a with version %d" % (a.__VERSION__,)) This is stored in :file:`${{EUPS_PATH}}/a/v1`. Adding an EUPS Table File ------------------------- In order to make our package usable, we need EUPS to ensure: - That :file:`a.py` is on ``PYTHONPATH`` (so that we can import it); - That :file:`bin/a` is on ``PATH`` (so that we can execute it). We communicate this to EUPS through a *table file*, located in the :file:`eups` directory within the product (in this case, :file:`${{EUPS_PATH}}/a/v1/ups/a.table`). Our file contains:: % cat ups/a.table envPrepend(PYTHONPATH ${PRODUCT_DIR}/python) envPrepend(PATH ${PRODUCT_DIR}/bin) When we ask EUPS to enable ("set up") the product, it will manipulate the environment in the obvious way. Of course, pre-pending things to environment variables (``envPrepend``) isn't all it can do: we'll see some more commands shortly. Declaring the Product to EUPS ----------------------------- We next *declare* the product to EUPS, causing it to read the table file and record information about the product in its database. The general form of the declaration command is:: % eups declare [PRODUCT_NAME} [VERSION] -r [PATH] In this case we execute:: % eups declare a v1 -r ${EUPS_PATH}/a/v1 Having thus declared the product, we can query the EUPS database for the list of all products it is tracking:: % eups list a v1 current And then we can set up the product using the :command:`setup` command, use it, and tear it down again with :command:`unsetup`:: % setup a % echo $PATH /tmp/eups_demo/a/v1/bin:… % a Package a with version 1 % eups list -s # Only lists products which have been set up. a v1 current setup % unsetup a % a -bash: a: command not found Managing Versions of Products ============================= Being able to :command:`(un)setup` a single version of a single product is of limited practical utility. However, EUPS lets us easily switch between different versions of the same product. We construct ``v2`` of ``a`` by simply copying the source to :file:`${{EUPS_PATH}}/a/v2` and incrementing the version number in the source. We then declare it to EUPS as before:: % eups declare a v2 -r ${EUPS_PATH}/a/v2 % eups list a v1 current a v2 Note that EUPS is now tracking two versions of ``a``. ``v`` is marked as ``current``: this indicates the version we get if we :command:`setup a` without further qualification:: % setup a % a Package a with version 1 % unsetup a % setup a v2 % a Package a with version 2 .. _tags: Tags ==== The ``current`` moniker we encountered above is just one example of a *tag*: a name associated with a particular combination of products and versions. EUPS defines some standard tags by default:: % eups tags current latest stable user:${username} ``current`` If you don’t do anything "clever", you’ll get the version tagged current when you set up a product. ``latest`` Reserved for special purposes: users should not interact with this tag. ``stable`` You can apply this tag at will; you might find it semantically meaningful. ``user:${username}`` Personal tag; apply at will. Omit the "user" when referring to it. We can apply tags to particular versions using :command:`eups declare` and then pass them as arguments to :command:`(un)setup`:: % eups list a v1 current a v2 % eups declare -t stable a v1 $ eups declare -t ${USER} a v2 % eups list a v1 current stable a v2 ${USER} % setup -t ${USER} a % a Package a with version 2 % setup a % a Package a with version 1 Note that when we don't specify a tag, we default to ``current``. Dependent Products ================== Frustrated by the limitations of ``a``, we now want to augment it with an additional product: ``b``. Again, the code is quite straightforward:: % cat bin/b #!/usr/bin/env python import a if __name__ == "__main__": print("Package b is using a version %d" % (a.__VERSION__,)) Note, though, that ``b`` imports ``a``: it is not possible to use ``b`` unless ``a`` has already been set up. We specify this dependency in the table file using the :command:`setupRequired` command:: % cat ups/b.table setupRequired(a) envPrepend(PATH, ${PRODUCT_DIR}/bin) We can :command:`declare` and :command:`setup` ``b``, and ``a`` is automatically loaded when required. Using the ``-v`` ("verbose") option with :command:`setup` makes this obvious:: % eups declare b v1 -r ${EUPS_PATH}/b/v1 % eups list a v1 current a v2 b v1 current % setup -v b Setting up: b Flavor: Darwin X86 Version: v1 Setting up: |a Flavor: Darwin X86 Version: v1 % b Package b is using a version 1 Versioned Dependencies ====================== Since we weren't specific about the version of ``a`` required by ``b``, EUPS just gives us the version tagged ``current``. We could override this in ``b``'s table file if required:: setupRequired(a v2) Sometimes, it's not enough to simply hard-code a versioned dependency in advance. For example, when dealing with compiled code, the version required may depend on the :abbr:`ABI (Application Binary Interface)` baked in at build time. EUPS provides the :command:`eups expandtable` command command to annotate a table file with the detailed state of the environment: it can be run at build time and the results stored for later use. For example:: % eups expandtable ups/b.table if (type == exact) { setupRequired(a -j v2) } else { setupRequired(a v2 [>= v2]) } envPrepend(PATH, ${PRODUCT_DIR}/bin) Passing the ``--exact`` flag to :command:`setup` on the command line will set up only the exact versions that are specified in the expanded table file; otherwise, EUPS assumes that any greater version is equally acceptable. For example, if we added a ``v3`` of ``a`` and removed ``v2``, an ``--exact`` setup would balk:: % eups list a v1 a v3 b v1 current % setup --exact b setup: in file /tmp/eups_demo/b/v1/ups/b.table: Product a v2 not found % setup -v --inexact b Setting up: b Flavor: Darwin X86 Version v1 Setting up: |a Flavor: Darwin X86 Version v3 Version Resolution ================== :ref:`Earlier ` we saw that we get the version tagged ``current`` unless we do something "clever". So what counts as clever? In fact, EUPS decides which version to load based on a user-configurable "Version Resolution Order" or VRO (analogous to Python's :abbr:`MRO (Method Resolution Order)`). The default VRO is:: % eups vro type:exact commandLine version versionExpr current This says: - Set things up in ``exact`` mode; - If possible, set up the version specified on the command line; - Otherwise, set up an explicit version specified elsewhere (e.g. in the table file); - Otherwise, choose a version based on an expression (e.g. >= 2.0) specified in the table file or elsewhere; - Otherwise, set up the version tagged ``current``. It is possible for users to customize the VRO, but this is only necessarily in exceptional cases and is outside the scope of this guide. The LSST Stack ============== We can now apply all the above to understand the structure of the LSST stack. :command:`eups list` will tell us about all the packages known to our copy of the stack, including tags and versions:: % . ${STACK_PATH}/loadLSST.bash % eups list activemqcpp 10.1 2015_05 b1327 b1326 […] […] Be aware that there are generally many packages and many, many tags, corresponding to different :doc:`CI ` runs, official releases, and so on. Setting up the ``lsst_apps`` product will, by default, give us the ``current`` version, and pull in all the products upon which it depends:: % setup -v lsst_apps Setting up: lsst_apps Flavor: DarwinX86 Version: 11.0+3 Setting up: |meas_deblender Flavor: DarwinX86 Version: 11.0+3 […] It's equally possibly to request other versions or tags of ``lsst_apps`` when required, and to apply tags like ``current`` or the ``user:`` tag to versions of particular interest for convenient access. It's occasionally informative to inspect the expanded table files of the installed products to see how version information was baked into the build:: % more ${LSST_APPS_DIR}/ups/lsst_apps.table if (type == exact) { setupRequired(meas_deblender -j 11.0+3) setupRequired(utils -j 11.0-1-g47edd16) […] :command:`eups distrib` ======================= :command:`eups distrib` is a package distribution mechanism which provides a convenient way of installing and updating the LSST stack. It is distinct from the core EUPS functionality described above, but is closely integrated and shares many concepts. :command:`eups distrib` reads details about available packages from a remote server. The appropriate location for finding LSST software is http://sw.lsstcorp.org/eupspkg. We can use :command:`eups distrib list` to list available software, and :command:`eups distrib install` to install it:: % eups distrib path http://sw.lsstcorp.org/eupspkg % eups distrib list lsst_apps lsst_apps generic 8.0.0.1+2 lsst_apps generic 8.0.0.1+3 […] % eups distrib install -t v11_0 lsst_apps Note that :command:`eups distrib list` does not list tags, even though :command:`eups distrib install` accepts a tag as a command line option (``-t v11_0``). The most convenient way to see a list of available tags is to visit the distribution server (https://sw.lsstcorp.org/eupspkg/tags/) in a web browser. Further Information =================== EUPS is developed outside the LSST stack in an `independent GitHub repository`_ which provides its own `issue tracker`_. However, it is important to track problems with installing the stack in :ref:`JIRA `, even if they are already known in the EUPS tracker. EUPS ships with a `manual`_, but it can be hard to read when getting started. There are also some tips on the `old LSST wiki`_. .. _independent GitHub repository: https://github.com/RobertLuptonTheGood/eups .. _issue tracker: https://github.com/RobertLuptonTheGood/eups/issues .. _manual: https://github.com/RobertLuptonTheGood/eups/blob/master/doc/eups.tex .. _old LSST wiki: https://dev.lsstcorp.org/trac/wiki/EupsTips