CEP 11 - Define the menuinst
standard
Title | Define the menuinst standard |
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 conda terms, this is the base environment |
DISTRIBUTION_NAME | Name of the base prefix directory; e.g. if BASE_PREFIX is /opt/my-project , this is my-project . |
PREFIX | Path to the target Python location. In conda terms, this is the path to the environment that contains the JSON file for this menu item. In some cases, it might be the same as BASE_PREFIX . |
ENV_NAME | Same as DISTRIBUTION_NAME , but for PREFIX . |
PYTHON | Path to the python executable in PREFIX . |
BASE_PYTHON | Path to the python executable in BASE_PREFIX . |
MENU_DIR | Path to the Menu directory in PREFIX . |
MENU_ITEM_LOCATION | Path to the main menu item artifact once installed. On Linux, this is the path to the .desktop file, on macOS it is the path to the .app directory, and on Windows it is the path to the Start Menu .lnk file. |
BIN_DIR | Path to the bin (Unix) or Library/bin (Windows) directories in PREFIX . |
PY_VER | Python major.minor version in PREFIX . |
SP_DIR | Path to Python's site-packages directory in PREFIX . |
HOME | Path to the user directory (~ ). |
ICON_EXT | Extension of the icon file expected by each platform. png in Linux, icns in macOS, and ico in Windows. Note the dot is not included. |
macOS-only | |
PYTHONAPP | Path to the python executable installed in PREFIX , assuming the python.app conda package is installed. Equivalent to {{ PREFIX }}/python.app/Contents/MacOS/python . |
Windows-only | |
SCRIPTS_DIR | Path to the Scripts directory in PREFIX . |
BASE_PYTHONW | Path to the pythonw.exe executable in BASE_PREFIX . |
PYTHONW | Path to the pythonw.exe executable in PREFIX . |
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.json
included in themy-package-1.2.3-h123abc.conda
artifact.
Expected behavior
Each platform MUST place the menu artifacts in these target locations:
Operating system | Artifact type | User location | System location | Notes |
---|---|---|---|---|
Linux | .desktop file | ~/.local/share/applications | /usr/local/share/applications | Some other user files are modified |
macOS | .app directory | ~/Applications | /Applications | |
Windows | .lnk file | {{ 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
.desktop
file and adjust/add the menu XML file(s). - On macOS, we had to come up with some ideas. The shortcut is actually an
.app
directory. Implementers must follow Apple's guidelines. See Addendum B for implementation details. - On Windows,
.lnk
files 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-menus
will create the menu items for the JSON files found in$PREFIX/Menu
.--rm-menus
will 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-prefix
is optional and defaults to the value passed to--prefix
. It is only needed whenIMPLEMENTER
is running from a location other than--prefix
(e.g.base
vs a custom environment, or system Python and a virtual environment).--mode
is optional and defaults to the mark provided by the--base-prefix
location. If a.nonadmin
file is present there,mode=user
will be assumed. Otherwise,mode=system
will be assumed, with a fallback tomode=user
if 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 assumedbase
environment location. This is nowadays available asroot_prefix
but overriding this with environment variables (viaCONDA_ROOT_PREFIX
) is buggy inconda
and needs to be fixed.- Extend
shortcuts
with the ability of accepting values (true, false, or a list of strings).--shortcuts
would setshortcuts=True
, which is the default otherwise.--no-shorcuts
would setshortcuts=False
.--shortcuts pkg1 pkg2 ...
would setshortcuts=[pkg1, pkg2, ...]
, which would instructIMPLEMENTER
to 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)
menuinst
wiki 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 invokingROOT_PYTHON cwp.py PREFIX
.pyscript
: hardcodesscript
to bePREFIX/python.exe
and takes the value as the first (and only) argument.pywscript
: same as above, but usespythonw.exe
as the launcher to, theoretically, avoid launching a console window next to your application.webbrowser
: alias toPREFIX/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.plist
key-value pairs. - The shell script with the
precommand
+activate
+command
logic 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*-script
file, which is spawned in a subprocess. - In some cases, if an external binary is required, it needs to be symlinked into the
.app
directory 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.