CEP 11 - Define the menuinst standard
| Title | Define the menuinststandard | 
| Status | Accepted | 
| Author(s) | Jaime Rodríguez-Guerra <jaime.rogue@gmail.com> | 
| Created | Oct 14, 2021 | 
| Updated | Jul 28, 2023 | 
| Discussion | conda-incubator/ceps#8 | 
| Implementation | conda/menuinst@cep-devel | 
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119 when, and only when, they appear in all capitals, as shown here.
Abstract
menuinst is a library used by conda to install menu items that point to commands provided by
conda packages. It operates by discovering certain JSON files located in $PREFIX/Menu after
linking the package to the environment.
This library has primarily targeted Windows. The original project supported Linux and MacOS, but
menuinst was never used in practice on those platforms. As a result, the required JSON metadata
diverged significantly in each platform, and the implementations were not kept up to date.
This CEP will attempt to standardize the menuinst interface by:
- Providing a unified metadata schema for all platforms so a single document contains all the metadata required to create shortcuts in all platforms.
- Enumerating the expected behavior for different configurations.
- Defining a programmatic interface for implementers (CLI / API).
Menu metadata schema
The full JSON schema is defined in this document, but here you can see a simplified overview of all possible keys and their default values:
{
  "$id": "https://schemas.conda.io/menuinst-1.schema.json",
  "$schema": "https://json-schema.org/draft-07/schema",
  "menu_name": "REQUIRED",
  "menu_items": [
    {
      "name": "REQUIRED",
      "description": "REQUIRED",
      "command": [
        "REQUIRED",
      ],
      "icon": None, # path to ico / png / icns file
      "precreate": None, # command to run before the shortcut is created
      "precommand": None, # command to run before activation and 'command'
      "working_dir": None, # starting working location for the process
      "activate": true,  # activate conda environment before running 'command'
      "terminal": false, # open in terminal and leave it open
      "platforms": {
        # To create the menu item for a fiven platform, the key must be present in this
        # dictionary. Presence is enough; the value can just be the empty dictionary: {}.
        "linux": {
          # See XDG Desktop standard for details
          # https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#recognized-keys
          "Categories": None,
          "DBusActivatable": None,
          "GenericName": None,
          "Hidden": None,
          "Implements": None,
          "Keywords": None,
          "MimeType": None,
          "NoDisplay": None,
          "NotShowIn": None,
          "OnlyShowIn": None,
          "PrefersNonDefaultGPU": None,
          "StartupNotify": None,
          "StartupWMClass": None,
          "TryExec": None,
          #: Map of custom MIME types to their corresponding glob patterns (e.g. ``*.txt``).
          "glob_patterns": None
        },
        "osx": {
          # See Apple docs for CF* and LS* variables
          # CF*: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
          # LS*: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html
          "CFBundleDisplayName": None,
          "CFBundleIdentifier": None,
          "CFBundleName": None,
          "CFBundleSpokenName": None,
          "CFBundleVersion": None,
          "CFBundleURLTypes": None,
          "CFBundleDocumentTypes": None,
          "LSApplicationCategoryType": None,
          "LSBackgroundOnly": None,
          "LSEnvironment": None,
          "LSMinimumSystemVersion": None,
          "LSMultipleInstancesProhibited": None,
          "LSRequiresNativeExecution": None,
          "UTExportedTypeDeclarations": None,
          "UTImportedTypeDeclarations": None,
          #: list of permissions to request for the app
          #: see https://developer.apple.com/documentation/bundleresources/entitlements
          "entitlements": None,
          #: symlink a file (usually the executable in 'command') into the .app directory
          "link_in_bundle": None,
          #: shell logic that will run when an Apple event is received
          "event_handler": None,
        },
        "win": {
          "desktop": true, # create desktop location
          "quicklaunch": true, # create quick launch shortcut too
          "file_extensions": None, # file extensions to associate with shortcut in registry
          "url_protocols": None, # URI protocols to associate with shortcut in registry
          "app_user_model_id": None, # Identifier used to associate processes with a taskbar icon
        }
      }
    }
  ]
}
Note that each platforms sub-dictionary (linux, macos, win) can override the global values
of their menu_items[*] entry (e.g. redefining command to adjust the shell syntax).
Each JSON file MUST be validated against its $id schema at build time; e.g in conda-build.
Placeholders
Each platform MUST provide these placeholders, to be used in the value of any str-accepting key.
To be replaced, they MUST be wrapped in double curly braces: {{ NAME }}.
| Placeholder | Value | 
|---|---|
| BASE_PREFIX | Path to the base Python location. In condaterms, this is thebaseenvironment | 
| DISTRIBUTION_NAME | Name of the base prefix directory; e.g. if BASE_PREFIXis/opt/my-project, this ismy-project. | 
| PREFIX | Path to the target Python location. In condaterms, this is the path to the environment that contains the JSON file for this menu item. In some cases, it might be the same asBASE_PREFIX. | 
| ENV_NAME | Same as DISTRIBUTION_NAME, but forPREFIX. | 
| PYTHON | Path to the pythonexecutable inPREFIX. | 
| BASE_PYTHON | Path to the pythonexecutable inBASE_PREFIX. | 
| MENU_DIR | Path to the Menudirectory inPREFIX. | 
| MENU_ITEM_LOCATION | Path to the main menu item artifact once installed. On Linux, this is the path to the .desktopfile, on macOS it is the path to the.appdirectory, and on Windows it is the path to the Start Menu.lnkfile. | 
| BIN_DIR | Path to the bin(Unix) orLibrary/bin(Windows) directories inPREFIX. | 
| PY_VER | Python major.minorversion inPREFIX. | 
| SP_DIR | Path to Python's site-packagesdirectory inPREFIX. | 
| HOME | Path to the user directory ( ~). | 
| ICON_EXT | Extension of the icon file expected by each platform. pngin Linux,icnsin macOS, andicoin Windows. Note the dot is not included. | 
| macOS-only | |
| PYTHONAPP | Path to the pythonexecutable installed inPREFIX, assuming thepython.appconda package is installed. Equivalent to{{ PREFIX }}/python.app/Contents/MacOS/python. | 
| Windows-only | |
| SCRIPTS_DIR | Path to the Scriptsdirectory inPREFIX. | 
| BASE_PYTHONW | Path to the pythonw.exeexecutable inBASE_PREFIX. | 
| PYTHONW | Path to the pythonw.exeexecutable inPREFIX. | 
Packaging guidelines
conda packages that wish to create a shortcut at install time MUST provide a JSON file such that:
- The JSON contents MUST pass schema validation.
- The JSON file MUST be placed under $PREFIX/Menu.
- The JSON filename MUST be <package-name>.json.
- Packaging tools (e.g. conda-build) MUST check the above conditions are met when the package is being created.
One example of a properly placed JSON file would be
$PREFIX/Menu/my-package.jsonincluded in themy-package-1.2.3-h123abc.condaartifact.
Expected behavior
Each platform MUST place the menu artifacts in these target locations:
| Operating system | Artifact type | User location | System location | Notes | 
|---|---|---|---|---|
| Linux | .desktopfile | ~/.local/share/applications | /usr/local/share/applications | Some other user files are modified | 
| macOS | .appdirectory | ~/Applications | /Applications | |
| Windows | .lnkfile | {{ menu_name }}directory inside Start Menu, Desktop, and/or Quick Launch | Start Menu | These locations are customizable and configured in the Windows registry. | 
- On Linux, little needs to be done because XDG delegates the responsibility to the desktop
manager. The implementer only needs to create the .desktopfile and adjust/add the menu XML file(s).
- On macOS, we had to come up with some ideas. The shortcut is actually an .appdirectory. Implementers must follow Apple's guidelines. See Addendum B for implementation details.
- On Windows, .lnkfiles are created with the Windows API. File type and URL protocol association is done in the Windows registry.
Some installations might provide two modes: "Current user only", and "All users". This option is not surfaced in the JSON metadata, but might be requested at creation time in the CLI or API. This means that implementers MUST be able to handle both user locations and system locations, as detailed above. In particular, in-process permission elevation needs to be considered.
When a package is removed, the file artifacts MUST be deleted too. If changes were done in other resources (XML files on Linux, Registry on Windows), these MUST be undone too.
CLI interface
The implementer CLI is not defined in this document. However, the integrations with constructor
SHOULD be standardized if they are going to be kept in use.
The proposed CLI (inspired by what's already in use to introduce minimal changes) is:
${IMPLEMENTER} constructor --prefix ${PREFIX} [--base-prefix ${BASE_PREFIX}] [--mode user|system] [--make-menus | --rm-menus] [pkg-name ...]
- --make-menuswill create the menu items for the JSON files found in- $PREFIX/Menu.
- --rm-menuswill uninstall the corresponding menu items from the system.
- If values are passed next to these two flags, only the JSON files that match those package names will be handled. Others will be ignored.
- --base-prefixis optional and defaults to the value passed to- --prefix. It is only needed when- IMPLEMENTERis running from a location other than- --prefix(e.g.- basevs a custom environment, or system Python and a virtual environment).
- --modeis optional and defaults to the mark provided by the- --base-prefixlocation. If a- .nonadminfile is present there,- mode=userwill be assumed. Otherwise,- mode=systemwill be assumed, with a fallback to- mode=userif necessary.
Alternatively, the constructor subcommand needs for menus can be dropped if IMPLEMENTER
supports new settings and/or CLI flags in the create | install commands. Namely:
- base_prefix: override the assumed- baseenvironment location. This is nowadays available as- root_prefixbut overriding this with environment variables (via- CONDA_ROOT_PREFIX) is buggy in- condaand needs to be fixed.
- Extend shortcutswith the ability of accepting values (true, false, or a list of strings).- --shortcutswould set- shortcuts=True, which is the default otherwise.
- --no-shorcutswould set- shortcuts=False.
- --shortcuts pkg1 pkg2 ...would set- shortcuts=[pkg1, pkg2, ...], which would instruct- IMPLEMENTERto handle menu item creation or removal for those packages only.
 
Backwards compatibility
Windows users do depend on the existing menuinst 1.x "schema". There are a lot of packages that
use it. This (unversioned) document needs to be kept around and respected. In the absence of the
$schema or $id keys, it will be assumed that the metadata is built with the legacy schema.
See Addendum A below for a best effort in documenting it.
References
- Rework linux/osx support plus new simplified format?
- Mamba's implementation in C++
- Interactions between conda, conda-standalone, constructor and menuinst
- Change the API to menuinst.install(path_or_dict)
- menuinstwiki as of 2021.10.18
- freedesktop.org specification
- Core Foundation Keys (info.plist)
- File type association in Windows
- Default programs in Windows
Copyright
All CEPs are explicitly CC0 1.0 Universal.
Addendum A
menuinst 1.x pre-standard
The required metadata for each platform is documented in the menuinst wiki. However, only
Windows is really supported by the tool. This asymmetrical growth has allowed Windows to grow an
ad-hoc specification that doesn't really translate well to other platforms.
The overall schema seems to be:
{
  "menu_name": str,
  "menu_items": list of dict,
}
Unfortunately, each menu item dict (let's call it MenuItem) takes a different form in each
platform.
MenuItem on Windows
{
  ["system" | "script" | "pyscript" | "pywscript" | "webbrowser"]: str,
  "scriptargument": str,
  "scriptarguments": list of str,
  "name": str,
  "workdir": str,
  "icon": str,
  "desktop": bool,
  "quicklaunch": bool,
}
Currently allowed placeholders are:
- ${PREFIX}: Python environment prefix
- ${ROOT_PREFIX}: Python environment prefix of root (conda or otherwise) installation
- ${PYTHON_SCRIPTS}: Scripts folder in Python environment,- ${PREFIX}/Scripts
- ${MENU_DIR}: Folder for menu config and icon files,- ${PREFIX}/Menu
- ${PERSONALDIR}: Not sure
- ${USERPROFILE}: User's home folder
- ${ENV_NAME}: The environment in which this shortcut lives.
- ${DISTRIBUTION_NAME}: The name of the folder of the root prefix, for example "Miniconda" if distribution installed at "C:\Users\Miniconda".
- ${PY_VER}: The Python major version only. This is taken from the root prefix. Used generally for placing shortcuts under a menu of a parent installation.
- ${PLATFORM}: one of (32-bit) or (64-bit). This is taken from the root prefix. Used generally for placing shortcuts under a menu of a parent installation.
MenuItem on MacOS
{
    "cmd": str,
    "name": str,
    "icns": str,
}
Currently allowed placeholders are:
- ${BIN_DIR}:- PREFIX/bin
- ${MENU_DIR}:- PREFIX/Menu
MenuItem on Linux
{
    "cmd": list of str,
    "id": str,
    "name": str,
    "comment": str.
    "terminal": bool,
    "icon": str,
},
On Linux, only cmd can take two special placeholders {{FILEBROWSER}} and {{WEBBROWSER}},
which are replaced by the default Desktop file explorer, and the default web browser, respectively.
Identified problems
The command interface
Windows has several ways to specify which command should be run with the shortcut:
- system+- scriptargument[s]: path to executable plus its arguments.
- script+- scriptargument[s]: same as above, but the executable is run in a subprocess after invoking- ROOT_PYTHON cwp.py PREFIX.
- pyscript: hardcodes- scriptto be- PREFIX/python.exeand takes the value as the first (and only) argument.
- pywscript: same as above, but uses- pythonw.exeas the launcher to, theoretically, avoid launching a console window next to your application.
- webbrowser: alias to- PREFIX/python -m webbrowser -t URL.
On Linux the command is specified with cmd, expressed as a list of strings. On MacOS, cmd is
also taken, but in this case it's expected to be a raw string.
The icon key
Windows and Linux expect icon. MacOS expects icns. Each platform requires a different file
format, but that can be arranged with placeholders.
Standardize the placeholders
Allowed placeholders vary vastly across platforms. A common subset must be identified and implemented. Per-platform options are allowed but only when strictly necessary.
Addendum B: Implementation details in macOS
- Most of the macOS-specific settings map to the .app'sInfo.plistkey-value pairs.
- The shell script with the precommand+activate+commandlogic is located in<NAME>.app/Contents/MacOS/<NAME>-script.
- A binary launcher is required for correct system integration (see reasons
conda/menuinst#123). This is placed at<NAME>.app/Contents/MacOS/<NAME>. The proposed launcher simply guesses its own location to find the*-scriptfile, which is spawned in a subprocess.
- In some cases, if an external binary is required, it needs to be symlinked into the .appdirectory to ensure keyboard integrations work (seeconda/menuinst#122).
- URL protocol association requires special support in the binary launcher. Implementers can choose how to implement it. See this issue and this PR for ideas.