.. _exts-main-doc: Extensions ########## What is an Extension? ********************* An extension is, in it's simplest form, just a folder with a config file (``extension.toml``). The Extension system will find that extension and if it's enabled it will do whatever the config file tells it to do, which may include loading python modules, **Carbonite** plugins, shared libraries, applying settings etc. There are many variations, you can have an extension whose sole purpose is just to enable 10 other extensions for example, or just apply some settings. Extension in a single folder **************************** Everything an extension has should be in or nested inside it's root folder. This is a convention we are trying to adhere to. Looking at other package managers, like those in the Linux ecosystem, they often spread the content of packages across the filesystem, which makes some things easier (like loading shared libraries), but also creates problems. Following this convention makes the installation step very simple - we just have to unpack a single folder A typical extension looks like this: .. code:: text [extensions-folder] └── omni.appwindow │──bin │ └───windows-x86_64 │ └───debug │ └─── omni.appwindow.plugin.dll │───config │ └─── extension.toml └───omni └───appwindow │─── _appwindow.cp36-win_amd64.pyd └─── __init__.py This example contains a **Carbonite** plugin and a python module (which contains bindings to this plugin). Single File Extensions ********************** Single file Extensions are supported - i.e. Extensions consisting only of a config file, without any code. In this case the name of the config will be used as the extension ID. This is used to make a top-level extensions which we call an **app**. They are used as an application entry point, to unroll the whole extension tree. The file extension can be anything (``.toml`` for instance), but the recommendation is to name them with the ``.kit`` file extension, so that it can be associated with the ``kit.exe`` executable and launched with a single click. .. code:: text [extensions-folder] └── omni.exp.hello.kit App Extensions ************** When ``.kit`` files are passed to ``kit.exe`` they are treated specially: This: ``> kit.exe C:/abc/omni.exp.hello.kit`` Is the same as: ``> kit.exe --ext-path C:/abc/omni.exp.hello.kit --enable omni.exp.hello`` It adds this ``.kit`` file as an extension and enables it, ignoring any default app configs, and effectively starting an app. Single file (``.kit`` file) extensions are considered apps, and "launch" button is added to it in the UI of the extension browser. For regular extension specify keyword: ``package.app = true`` in config file to mark your extension as an app, one users can launch from. App extensions can be published, versioned etc. just like normal extensions. So for instance if the ``omni.exp.hello`` from above is published, we can just run Kit as: ``> kit.exe omni.exp.hello.kit`` Kit will pull it from the registry and start. Extension Search Paths ************************ Extensions are automatically searched for in specified folders. Core **Kit** config ``kit-core.json`` specifies default search folders in ``/app/exts/foldersCore`` setting. This way **Kit** can find core extensions, it also looks for extensions in system specific documents folder for user convenience. To add more folders to search paths there are a few ways: 1. Pass ``--ext-folder [PATH]`` CLI param to kit. 2. Add to array in settings: ``/app/exts/folders`` 3. Use the ``omni::ext::ExtensionManager::addPath`` API to add more folders (also available in python). To specify direct path to a specific extension use ``/app/exts/paths`` setting or the ``--ext-path [PATH]`` CLI parameter. Folders added last are searched first. This way they will be prioritized over others, allowing user to override certain extensions. Example of adding extension seach path in a kit file: .. code:: toml [settings.app.exts] folders.'++' = [ "C:/hello/my_extensions" ] Custom Search Paths Protocols ============================== Both folders and direct paths can be extended to support other url schemes. If no scheme specified they are assumed to be local filesystem. Extension system provides API to implement custom protocol. This way an extension can be written to enable searching for extensions in different locations, for example: git repos. E.g. ``--ext-folder foo://abc/def`` -- extension manager will redirect that search paths to implementor of ``foo`` scheme, if it was registered. .. _git-url-paths: Git URL as Extension Search Paths ================================== Extension ``omni.kit.extpath.git`` implemennts following extension search path schemes: ``git``, ``git+http``, ``git+https``, ``git+ssh``. Optional URL query params are supported: * ``dir`` subdirectory of a git repo to use as a search paths (or direct path). * ``branch`` git branch to checkout * ``tag`` git tag to checkout * ``sha`` git sha to checkout Example of usage with cmd arg: > ``--ext-folder git://github.com/bob/somerepo.git?branch=main&dir=exts`` -- Add ``exts`` subfolder and ``main`` branch of this git repo as extension search paths. Example of usage in kit file: .. code:: toml [settings.app.exts] folders.'++' = [ "git+https://gitlab-master.nvidia.com/bob/some-repo.git?dir=exts&branch=feature" ] After first checkout git path is cached into global cache. To pull updates: * use extension manager properties pages * setting: ``--/exts/omni.kit.extpath.git/autoUpdate=1`` * API call ``omni.kit.extpath.git.update_all_git_paths()`` The Extension system automatically enables this extension if path with a scheme is added. It enables extensions specified in a setting: ``app/extensions/pathProtocolExtensions``, which by default is ``["omni.kit.extpath.git"]``. .. note:: Git installation is required for this functionality. It expects ``git`` cmd to be available in system shell. Extension Discovery ******************* The Extension system monitors specified extension search folders (or direct paths) for changes. It automatically syncs all changed/added/removed extensions. Any subfolder which contains an ``extension.toml`` in the root or ``config`` folder is considered to be an extension. The subfolder name uniquely identifies the extension and is used to extract the extension name and tag: ``[ext_name]-[ext_tag]`` | [extensions-folder] | └── omni.kit.example-gpu-2.0.1-stable.3+cp36 | ├── extension.toml | └── ... In this example we have an extension ``omni.kit.example-gpu-2.0.1-stable.3+cp36``, where: - **name:** ``omni.kit.example`` - **tag:** ``gpu`` (optional, default is "") The version and other information (like supported platforms) are queried in the extension config file (see below). They may also be included in the folder name, which is what the system does with packages downloaded from a remote registry, but that is not "the ground truth" for that data. So in this example anything could have been after the "gpu" tag, e.g. ``omni.kit.example-gpu-whatever``. Extension Dependencies ********************** When a Kit-based application starts it discovers all extensions and does nothing with them until some of the extensions are enabled. They can be enabled via config file or API. Each extension can depend on other extensions and this is where the whole application tree can unroll. The user may enable a high level extension like **omni.usd_viewer** which will bring in dozens of others. An extension can express dependencies on other extensions using **name**, **version** and optionally **tag**. It is important to keep extensions versioned properly and express breaking changes using `Semantic Versioning `_. This is a good place to grasp what **tag** is for. If say extension ``foo`` depends on ``bar`` you may implement other versions of ``bar``, like: ``bar-light``, ``bar-prototype``. If they still keep the contract the same (public API, expected behavior etc.) you can safely substitute ``bar`` without ``foo`` noticing. In other words if the extension is an interface, **tag** is the implementation. The effect is that just enabling some high level extensions like omni.kit.window.script_editor will expand the whole dependency tree in the correct order without the user having to specify all of them or worry about initialization order. One can also substitute extensions in a tree with the different version or tag, running the same end user experience, but with a different low-level building block. When an extension is enabled the manager tries to satisfy all of it's dependencies by recursively solving the dependency graph. This is a difficulty problem - If dependency resolution succeeds the whole dependency tree is enabled in order so that all dependents are enabled first. The opposite is true for disabling of extensions. All extensions who depend on our extension are disabled first. More details on the dependency system can be found in the C++ unit tests: ``source/tests/test.unit/ext/TestExtensions.cpp`` A Dependency graph defines the order in which extensions are loaded - it is sorted topologically. There are however, many ways to sort the same graph (think of independent branches). To give finer control over startup order, the ``order`` parameters can be used. Each extension can use ``core.order`` config parameter to define it's order and the order of dependencies can also be overriden with ``order`` param in ``[[dependencies]]`` section. Those with lower order will start as soon as possible. If there are multiple extensions that depend on one extension and are trying to override this order then the one that is loaded last will be used (according to dependency tree). In summary - the dependency order is always satisfied (or extensions won't be started at all, if the graph contains cycles) and soft ordering is applied on top using config params. Extension configuration file (extension.toml) ********************************************* An Extension config file can specify: 1. Dependencies to import 2. Settings 3. A variety of metadata/information which are used by the Extension Registry browser (for example) TOML is the format used . See this `this short tutorial `_. . Note in particular ``[[]]`` TOML syntax for array of objects and quotes around keys which contain special symbols(e.g. ``"omni.physx"``). The config file should be placed in the root of the extension folder or in a ``config`` subfolder. .. note:: All relative paths in config are relative to the extension folder. All paths accept tokens. (like ``${platform}``, ``${config}``, ``${kit}`` etc). More info: :ref:`list-tokens`. There are no mandatory fields in a config, so even with empty config extension will be considered valid and can be enabled - without any effect. Next we will list all the config fields the extension system uses, a config file may contain more. The Extension system provides a mechanism to query config files and hook into itself. That allows us to extend the extension system itself and add new config sections. For instance :mod:`omni.kit.pipapi` allows extensions to specify pip packages to be installed before enabling them. More info on that: :ref:`ext-manager-hooks`. That also means that **typos or unknown config settings will be left as is and no warning will be issued**. Config Fields ============= ``[core]`` section -------------------- For generic extension properties, used directly by the Extension Manager core system. ``[core.reloadable]`` (default: ``true``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Is the extension **reloadable**? The Extension system will monitor the extension's files for changes and try to reload the extension when any of them change. If the extension is marked as **non-reloadable** all other extensions that depend on it are also non-reloadable. ``[core.order]`` (default: ``0``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When extensions are independent of each other they are enabled in an undefined order. An extension can be ordered to be before (negative) or after (positive) other extensions ``[package]`` section --------------------- Contains information used for publishing extensions and displaying user facing details about the package. ``[package.version]`` (default: ``"0.0.0"``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Extension version. This setting is required to be able to publish extensions to the remote registry. The `Semantic Versioning `_ concept is baked into theextension system, so make sure to follow the basic rules: * Before you reach ``1.0.0``, anything goes, but if you make breaking changes, increment the minor version. * After ``1.0.0``, only make breaking changes when you increment the major version. * Incrementing the minor version implies a backward compatible change. Let's say extensions ``bar`` depends on ``foo-1.2.0``. That means that ``foo-1.3.0``, ``foo-1.4.5``, etc.. are also suitable and can be enabled by extension system. * Use version numbers with three numeric parts such as ``1.0.0`` rather than ``1.0``. * Prerelease labels can also be used like so: ``1.3.4-stable`` or ``1.3.4-rc1.test.1``. ``[package.title]`` default: ``""`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ User facing package name, used for UI. ``[package.description]`` default: ``""`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ User facing package description, used for UI. ``[package.category]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Extension category, used for UI. One of: - ``animation`` - ``graph`` - ``rendering`` - ``audio`` - ``simulation`` - ``example`` - ``internal`` - ``other`` ``[package.app]`` (default: ``false``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Extension is an App. Used for UI to mark extension as an app. It adds "Launch" button to run kit with only this extesnion enabled. For single file extensions (``.kit`` files it defaults to ``true``). ``[package.feature]`` (default: ``false``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Extension is a Feature. Used for UI, to show user facing extensions, suited to be enabled by user from UI. By default app can choose to show only those extensions in the list. ``[package.toggleable]`` (default: ``true``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Indicates whether an extension can be toggled (i.e enabled/disabled) in the UI by user. There is another related setting: ``[core.reloadable]``, which can prevent the user from disabling an extension in the UI. ``[package.authors]`` (default: ``[""]``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lists people or organizations that are considered the "authors" of the package. An optional email address may be included within angled brackets at the end of each author. ``[package.repository]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ URL of the extension source repository, used for UI. ``[package.keywords]`` (default: ``[""]``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Array of strings that describe this extension. This can be helpful when searching for it in an Extension registry. ``[package.changelog]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Location of a CHANGELOG.MD file in the target (final) folder of the Extension, and relative to it's root. The UI will load and show it. We can also insert the content of that file inline instead of specifying a filepath. It is important to keep the changelog updated when new versions of an extension are released and published. For more info on writing changelog refer to `Keep a Changelog `_ ``[package.readme]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Location of README file in the target (final) folder of an extension, relative to the root. UI will load and show it. We can also insert the content of that file inline instead of specifying a filepath ``[package.preview_image]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Location of a preview image in the target (final) folder of extension, relative to the root. The preview image is shown in the "Overview" of Extensions window. A screenshot of your extension might make a good preview image. ``[package.icon]`` (default: ``""``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Location of icon in the target (final) folder of extension, relative to the root. Icon is shown in Extensions window. It is recommended that it be 256x256 pixels in size ``[package.target]`` ^^^^^^^^^^^^^^^^^^^^ This section is used to describe the target platform this extension runs on - this is fairly arbitrary, but can include: - Operating system - CPU architecture - Python version - Build configuration The Extension system will filter out extensions that doesn't match the current environment/platform. This is particularly important for extensions published and downloaded from a remote Extension registry. **Normally you don't need to fill this section in manually**. When extension being published it will be automatically filled in with defaults, more in :ref:`ext-publishing`. But it can be overriden by setting: ``package.target.kit`` (default: ``["*"]`` -- Kit version (without metadata), e.g. ``"101.0"``, ``"102.3"``. ``package.target.config`` (default: ``["*"]`` -- Build config, e.g. ``"debug"``, ``"release"``. ``package.target.platform`` (default: ``["*"]`` -- Build platform, e.g. ``"windows-x86_64"``, ``"linux-aarch64"``. ``package.target.python`` (default: ``["*"]`` -- Python version, e.g. ``"cp36"`` (cpython 3.6). Refer to :pep:`0425`. Wildcards can be used. A full example: .. code:: toml [package.target] config = ["debug"] platform = ["linux-*", "windows"] python = ["*"] ``[package.writeTarget]`` ^^^^^^^^^^^^^^^^^^^^^^^^^ This section can be used to explicitly control if ``[package.target]`` should be written. By default it is written based on rules described in :ref:`ext-publishing`. But if for some ``[target]`` a field set: ``package.writeTarget.[target] = true/false`` that tells explicitly if it should be automatically filled in. For example if you want to write kit version as a target to make extension work **only** on that version, set: .. code:: toml [package] writeTarget.kit = true Or if you want your extension to work for all python versions, write: .. code:: toml [package] writeTarget.python = false List of known targets is the same as in ``[package.target]`` section: ``kit``, ``config``, ``platform``, ``python``. ``[dependencies]`` section -------------------------- This section is used to describe which extensions this extension depends on. The extension system will guarantee to enable them before it loads your extension (assuming that it doesn't fail and not enable your extension at all). One can optionally specify version and tag per dependency and make a dependency optional. Each entry is a **name** of other extension. It may or may not additional specifications such as **tag**, **version**, **optional**, **exact**: .. code:: toml "omni.physx" = { version="1.0", "tag"="gpu" } "omni.foo" = { version="3.2" } "omni.cool" = { optional = true } **Note that it is highly recommended to use versions.** as it will keep extensions (and the whole application) more stable - i.e if breaking change happens in a dependency and dependents have not yet been updated, an older version can still be used. (only the *major* and *minor* parts are needed for thisaccording to semver). ``optional`` (default: ``false``) -- will mean that if the extension system can't resolve this dependency the extension will still be enabled. So it is expected that the extension can handle the absence of this dependency. Optional dependencies are not enabled unless they are the a non-optional dependency of some other extension which is enabled or if they are enabled explicitly (using API, settings, CLI etc.). ``exact`` (default: ``false``) -- exact version match of extension will be used. This flag is experimental and may change. ``order`` (default: ``None``) -- override ``core.order`` parameter of extension it depends on. Only applied if set. ``[python]`` section -------------------- If an extension contains python modules or scripts this is where to specify them ``[[python.module]]`` ^^^^^^^^^^^^^^^^^^^^^ Specifies python module(s) that are part of this extension. Multiples can be specified, notice the ``[[]]`` syntax. When an extension is enabled, modules are imported in order. Here we specify 2 python modules to be imported (``import omni.hello`` and ``import omni.calculator``). When modules are scheduled for import this way, they will be reloaded if the module is already present. Example: .. code:: toml [[python.module]] name = "omni.hello" [[python.module]] name = "omni.calculator" path = "." public = true ``name`` (required) -- python module name, can be empty. Think of it as what will be imported by other extensions that depend on you: .. code:: py import omni.calculator ``public`` (default: ``true``) -- If public, a module will be available to be imported by other extensions (extension folder is added to sys.path). Non-public modules have limited support and their use is not recommended. ``path`` (default: ``"."``) -- Path to the root folder where the python module is located. If relative it is relative to extension root. Think of it as what gets added to ``sys.path``. By default the extension root folder is added if any ``[[python.module]]`` directive is specified. ``searchExt`` (default: ``true``) -- If true, imports said module and launches extensions search routine within the module. If false, module is only imported. ``[[python.scriptFolder]]`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Script folders can be added to ``IAppScripting``, and they will be searched for when a script file path is specified to executed (with --exec or via API). Example: .. code:: toml [[python.scriptFolder]] path = "scripts" ``path`` (required) -- Path to the script folder to be added. If the path is relative it is relative to the extension root. ``[native]`` section -------------------- Used to specify **Carbonite** plugins to be loaded ``[[native.plugin]]`` ^^^^^^^^^^^^^^^^^^^^^ When an Extension is enabled, the Extension system will search for **Carbonite** plugins using ``path`` pattern and load all of them. It will also try to acquire the ``omni::ext::IExt`` interface if any of the plugins implements it. That provides an optional entry point in C++ code where your extension can be loaded. When an extension is disabled it releases any acquired interfaces which may lead to plugins being unloaded. Example: .. code:: toml [[native.plugin]] path = "bin/${platform}/${config}/*.plugin" recursive = false ``path`` (required) -- Path to search for **Carbonite** plugins, may contain wildcards and tokens (see :ref:`list-tokens`). ``recursive`` (default: `false`) -- Search recursively in folders. ``[[native.library]]`` section ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Used to specify shared libraries to load when an Extension is enabled. When an Extension is enabled the Extension system will search for native shared libraries using ``path`` and load them. This mechanism is useful to "preload" libraries needed later, avoid OS specific calls in your code, and the use of ``PATH``/``LD_LIBRARY_PATH`` etc to locate and load DSOs/DLLs. With this approach we just load the libraries needed directly. When an extension is disabled it tries to unload those shared libraries. Example: .. code:: toml [[native.library]] path = "bin/${platform}/${config}/foo.dll" ``path`` (required) -- Path to search for shared libraries, may tokens (see :ref:`list-tokens`). ``[settings]`` section ---------------------- Everything under this section is applied to root of the global **Carbonite** settings (``carb.settings.plugin``). In case of conflict, the original setting is kept. It is good practice to namespace your settings with your extension name and put them all under ``exts`` root key, e.g.: .. code:: toml [settings] exts."omni.kit.renderer.core".compatibilityMode = true .. note:: Quotes are used here to distinguish between `.` of a toml file and `.` in the name of extension. An mportant detail is that settings are applied in reverse order of extension startup (before any extensions start) and they don't override each other. That means that a parent extension can specify settings for child extensions to use. ``[[env]]`` section ------------------- This section is used to specify one or more environment variables to set when an extension is enabled. Just like settings, env vars are applied in reverse order of startup. They don't by default override if already set, but override behaviour allows parent extensions to override env vars of extensions they depend on. Example: .. code:: toml [[env]] name = "HELLO" value = "123" isPath = false append = false override = false platform = "windows-x86_64" ``name`` (required) -- Environment variable name. ``value`` (required) -- Environment variable value to set. ``isPath`` (default: ``false``) -- Treat value as path. If relative it is relative to the extension root folder. Tokens can also be used as within any path. ``append`` (default: ``false``) -- Append value to already set env var if any. Platform specific separator will be used. ``override`` (default: ``false``) -- Override value of already set env var if any. ``platform`` (default: ``""``) -- Set only if platform matches pattern. Wildcard can be used. ``[fswatcher]`` section ----------------------- Used to specify file system watcher used by the Extension system to monitor for changes in extensions and auto reload. ``[fswatcher.patterns]`` ^^^^^^^^^^^^^^^^^^^^^^^^ Specify files that are monitored. ``include`` (default: ``["*.toml", "*.py"]``) -- File patterns to include. ``exclude`` (default: ``[]``) -- File patterns to exclude. Example: .. code:: toml [fswatcher.patterns] include = ["*.toml", "*.py", "*.txt"] exclude = ["*.cache"] ``[fswatcher.paths]`` ^^^^^^^^^^^^^^^^^^^^^^^^ Specify folders that are monitored. FS watcher will use OS specific API to listen for changes on those folders. You can use that setting that limit amount of subscriptions if your extensions has too many folders inside. Simple path as string pattern matching is used, where '*' means any amount of symbols. Pattern like ``*/123*`` will match ``abc/123/def``, ``abc/123``, ``abc/1234``. '/' path separator is used on all platforms. Matching paths are relative to extension root and they begin and end with '/'. ``include`` (default: ``["*/config/*", "*/./*"]`` and python modules) -- Folder path patterns to include. ``exclude`` (default: ``["*/__pycache__/*", "*/.git/*"]``) -- Folder path patterns to exclude. Example: .. code:: toml [fswatcher.paths] include = ["*/config"] exclude = ["*/data*"] ``[[test]]`` section -------------------- This section is read only by testing system (``omni.kit.test`` extension) when running per extension tests. Extension test is a separate process which only enables tested extension and runs all the tests it has. Usually this section can be left empty, but extension can specify to add additional extension (which is not a part of regular dependencies, or dependency is optional), additional cmd arguments, filter out (or in) any additional stdout messages. Each ``[[test]]`` entry will run separate extension test process. Extension tests run in the context of an app. An app can be empty, that makes extension test isolated and only its dependencies are enabled. Testing in an empty app is minimal recommended test coverage. Extension developer can then opt-in to be tested in other apps and fine-tune test settings per app. Example: .. code:: toml [[test]] name = "default" apps = [""] args = ["-v"] dependencies = ["omni.kit.capture"] pythonTests.include = ["omni.foo.*"] pythonTests.exclude = [] timeout = 180 parallelizable = true unreliable = false profiling = false waiver = "" stdoutFailPatterns.include = ["*[error]*", "*[fatal]*"] stdoutFailPatterns.exclude = [ "*Leaking graphics objects*", # Exclude grahics leaks until fixed ] ``name`` -- Test process name. If there are more than one ``[[test]]`` entry those names must be unique. ``apps`` -- List of apps to use that configuration for. Used in case there are multiple ``[[test]]`` entries. Wildcards are supported. Defaults to ``[""]`` which is an empty app. ``args`` -- Additional cmd arguments to pass into extension test process. ``dependencies`` -- List of additional extensions to enable when running extension test. ``pythonTests.include`` -- List of tests to run. If empty python modules used instead (``[[python.module]]``). Since all tests names start with module they defined in. Can contain wildcard symbol. ``pythonTests.exclude`` -- List of tests to exclude from running. Can contain wildcard symbol. ``timeout`` (default: ``180``) -- Test process timeout (in seconds). ``parallelizable`` (default: ``true``) -- Test process can run in parallel relative to other test processes. ``unreliable`` (default: ``false``) -- If marked as unreliable test failure won't fail whole test run. ``profiling`` (default: ``true``) -- Collects and outputs Chrome trace via carb profiling for CPU events for the test process. ``waiver`` -- String explaining why an extension contains no tests ``stdoutFailPatterns.include`` -- List of additional patterns to search in stdout and treat as a failure. Can contain wildcard symbol. ``stdoutFailPatterns.exclude`` -- List of additional patterns to exclude from being a test failure. Can contain wildcard symbol. Config Filters ============== Any part of config can be filtered based on current platform or build configuration. Use ``"filter:platform"."[platform_name]"`` or ``"filter:config"."[build_config]"`` pair of keys. Anything under those keys will be merged on top of tree they are located in (or filtered out if doesn't apply). +----------------+----------------------------------------+ | filter | values | +================+========================================+ | platform | ``windows-x86_64``, ``linux-x86_64`` | +----------------+----------------------------------------+ | config | ``debug``, ``release`` | +----------------+----------------------------------------+ To understand look at example: .. code:: toml [dependencies] "omni.foo" = {} "filter:platform"."windows-x86_64"."omni.fox" = {} "filter:platform"."linux-x86_64"."omni.owl" = {} "filter:config"."debug"."omni.cat" = {} After loading that extension on windows in debug build it will resolve to: .. code:: toml [dependencies] "omni.foo" = {} "omni.fox" = {} "omni.cat" = {} .. note:: You can debug it by running in debug mode, with ``--/app/extensions/debugMode=1`` setting and looking into log file. Example ======= Here is a full example of an ``extension.toml`` file: .. code:: toml [core] reloadable = true order = 0 [package] version = "0.1.0" category = "Example" feature = false app = false title = "The Best Package" description = "long and boring text.." authors = ["John Smith "] repository = "https://gitlab-master.nvidia.com/omniverse/kit" keywords = ["banana", "apple"] changelog = "docs/CHANGELOG.md" readme = "docs/README.md" preview_image = "data/preview.png" icon = "data/icon.png" # writeTarget.kit = true # writeTarget.platform = true # writeTarget.config = true # writeTarget.python = true [dependencies] "omni.physx" = { version="1.0", "tag"="gpu" } "omni.foo" = {} # Modules are loaded in order. Here we specify 2 python modules to be imported (``import hello`` and ``import omni.physx``). [[python.module]] name = "hello" path = "." public = false [[python.module]] name = "omni.physx" [[python.scriptFolder]] path = "scripts" # Native section, used if extension contains any Carbonite plugins to be loaded [[native.plugin]] path = "bin/${platform}/${config}/*.plugin" recursive = false # false is default, hence it is optional # Library section. Shared libraries will be loaded when the extension is enabled, note [[]] toml syntax for array of objects. [[native.library]] path = "bin/${platform}/${config}/foo.dll" # Settings. They are applied on the root of global settings. In case of conflict original settings are kept. [settings] exts."omni.kit.renderer.core".compatibilityMode = true # Environment variables. Example of adding "data" folder in extension root to PATH on windows: [[env]] name = "PATH" value = "data" isPath = true append = true platform = "windows-x86_64" # Fs Watcher patterns and folders. Specify which files are monitored for changes to reload an extension. Use wildcard for string matching. [fswatcher] patterns.include = ["*.toml", "*.py"] patterns.exclude = [] paths.include = ["*"] paths.exclude = ["*/__pycache__*", "*/.git*"] Extension Enabling/Disabling **************************** Extensions can be enabled and disabled at runtime using the provided API. Default **Create** comes with an Extension Manager UI which shows all available extensions and allow user to toggle them. An App configuration file can also used to control which extensions are to be enabled. You may also use command line arguments to the Kit executable (or any Omniverse App based on Kit) to enable specific extensions: Example: ``> kit.exe --enable omni.kit.window.console --enable omni.kit.window.extensions`` ``--enable`` adds extension to "enabled list". Command above will start only extensions needed to show those 2 windows. Python Modules ============== Enabling an extension loads the python modules specified and searches for children of :class:`omni.ext.IExt` class. They are instantiated and the ``on_startup`` method is called, e.g.: ``hello.py`` .. code-block:: python import omni.ext class MyExt(omni.ext.IExt): def on_startup(self, ext_id): pass def on_shutdown(self): pass When an extension is disabled ``on_shutdown`` is called and all references to the extension object are released. Native Plugins ============== Enabling an extension loads all Carbonite plugin specified by search masks in the ``native.plugin`` section. If one or more plugins implement ``omni::ext::IExt`` interface - is is acquired and the ``onStartup`` method is called. When an extension is disabled ``onShutdown`` is called and the interface is released. Settings ======== Settings to be applied when an extension is enabled can be specified in the ``settings`` section. They are applied on the root of global settings. In case of conflict the original settings are kept. It is recommended to use path ``exts/[extension_name]`` for extension settings, but in general any path can be used. It is also good practice to document settings in the ``extension.toml`` file, which makes it a great place to discover which settings a particular extension supports. .. _ext-tokens: Tokens ======== When extension is enabled it sets tokens into Carbonite ``ITokens`` interface with a path to extension root folder. E.g. for extension ``omni.foo-bar`` tokens ``${omni.foo}`` and ``${omni.foo-bar}`` are set. Extensions Manager ****************** Reloading ========= Extensions can be *hot reloaded*. The Extension system monitors the file system for changes to enabled extensions. If it finds any, the extensions are disabled and enabled again (which can involve reloading large parts of the dependency tree). That allows live editing of python code and recompilation of C++ plugins. Use ``fswatcher.patterns`` and ``fswatcher.paths`` config settings (see above) to control which files change triggers reloading. Use ``reloadable`` config setting to disable reloading. That will also block the reloading of all extensions this extension depends on. The extension can still be unloaded using API. New extensions can also be added and removed at runtime. Extension interfaces ==================== The Extension manager is implemented in ``omni.ext.plugin``, with an interface: ``omni::ext::IExtensions`` (C++) :doc:`../api/core/omni.ext` (python) It is loaded by `omni.kit.app` and you can get an extension manager instance using it's interface: ``omni::kit::IApp`` (C++) :doc:`../api/core/omni.kit.app` (python) Runtime Information =================== At runtime a user can query various pieces of information about each extension. Use ``omni::ext::IExtensions::getExtensionDict()`` to get a dictionary for each extension with all the relevant information. For python use :func:`omni.ext.ExtensionManager.get_extension_dict`. This dictionary contains: #. Everything the extension.toml contains under the same path #. It has an additional ``state`` section which contains: #. ``state/enabled`` (bool): Indicates if the extension is currently enabled. #. ``state/reloadable`` (bool): Indicates if the extension can be reloaded (used in the UI to disable extension unloading/reloading) .. _ext-manager-hooks: Hooks ===== Both the C++ and python APIs for the Extension system provide a way to hook into certain actions/phases of the Extension System to enable extending it. If you register a hook like this: .. code:: python def on_before_ext_enabled(self, ext_id: str, *_): pass manager = omni.kit.app.get_app_interface().get_extension_manager() self._hook = self._manager.get_hooks().create_extension_state_change_hook( self.on_before_ext_enabled, omni.ext.ExtensionStateChangeType.BEFORE_EXTENSION_ENABLE, ext_dict_path="python/pipapi", hook_name="python.pipapi", ) ..your callback will be called before each extension that contain the "python/pipapi" key in a config file is enabled. This allows us to write extension that extend the Extension system. They can define their own configuration settings and react to them when extensions which contain those settings get loaded. .. only:: internal Look at the C++ header file (``IExtensions.h``) or :obj:`omni.ext.IExtensionManagerHooks` for more hooks. Other Settings ============== ``/app/extensions/disableStartup`` (default: ``false``) ------------------------------------------------------- Special mode when extensions doesn't start (python and C++ startup functions are not called). Everything else works as usual. One usage might be to warm up everything and get extensions downloaded. Another use case is getting python environment setup without starting anything. ``/app/extensions/precacheMode`` (default: ``false``) ------------------------------------------------------- Special mode when all dependencies are solved and extensions downloaded, then app exits. It is useful for precaching all extensions before running an app to get everything downloaded and check that all dependencies are correct. ``/app/extensions/debugMode`` (default: ``false``) -------------------------------------------------- Output more debug information into info logging channel. ``/app/extensions/detailedSolverExplanation`` (default: ``false``) -------------------------------------------------------------------- Output more information after solver finishes explaining why certain versions were choosen and what available versions were (more costly). ``/app/extensions/registryEnabled`` (default: ``true``) ------------------------------------------------------- Disable falling back to extension registry when couldn't resolve all dependencies and fail immediately. ``/app/extensions/skipPublishVerification`` (default: ``false``) ---------------------------------------------------------------- Skip extension verification before publishing. Use wisely. ``/app/extensions/excluded`` (default: ``[]``) ---------------------------------------------- List of extensions to exclude from startup. Can be with or without version. Before solving startup order all those extensions are removed from all dependencies. ``/app/extensions/preferLocalVersions`` (default: ``true``) ----------------------------------------------------------- If ``true`` prefer local extension versions over remote ones during dependency solving. Otherwise all treated equally and it is likely to get newer versions selected and downloaded. ``/app/extensions/syncRegistryOnStartup`` (default: ``false``) -------------------------------------------------------------- Force sync with registry on startup. Otherwise registry is only enabled if dependency solving fails (something missing). Also `--update-exts`` command line switch enables that behavior. ``/app/extensions/publishExtraData`` (default: ``{}``) -------------------------------------------------------------- Extra data to write into extension ``package/publish`` namespace when published. ``/app/extensions/fsWatcherEnabled`` (default: ``true``) -------------------------------------------------------------- Global disable for all filesystem watcher ext system creates. Extension Registries ******************** The Extension system supports adding external registry providers for publishing extensions to, and pulling extensions from. By default ``Kit`` comes with ``omni.kit.registry.nucleus`` extension (:doc:`../../source/extensions/omni.kit.registry.nucleus/docs/index`) which adds support for Nucleus as an extension registry. When an extension is enabled the dependency solver resolves all dependencies. If a dependency is missing in the local cache it will ask the registry for a particular extension and it will be downloaded and installed at runtime. Installation is just unpacking of a zip archive into cache folder (``app/extensions/registryCache`` setting). The Extension system will only enable the Extension registry when it can't find all extensions locally. At that moment it tries to enable an extension specified in setting: ``app/extensions/registryExtension``, which by default is ``omni.kit.registry.nucleus``. The Registry system can be completely disabled with ``app/extensions/registryEnabled`` setting. The Extension manager provides an API to add other extension registries and query any existing ones ( ``omni::ext::IExtensions::addRegistryProvider``, ``omni::ext::IExtensions::getRegistryProviderCount`` etc). Multiple registries can be configured to be used ath the same time. They are uniquely identified with a name. Setting ``app/extensions/registryPublishDefault`` sets which one to use by default when publishing and unpublishing extensions. The API provides a way to explicitly pass the registry to use. .. _ext-publishing: Publishing Extensions ===================== To publish your extension to the registry use ``kit.exe --publish`` CLI command. E.g. Example: ``> kit.exe --publish omni.my.ext-tag`` If there is more than one version of this extension available it will produce an error saying you need to specify which one to publish. Example: ``> kit.exe --publish omni.my.ext-tag-1.2.0`` To specify in which registry to publish, override default registry name: Example: ``> kit.exe --publish omni.my.ext-tag-1.2.0 --/app/extensions/registryPublishDefault="kit/mr"`` If the extension already exist in the registry it will fail. To force overwrite use additional ``--publish-overwrite`` argument: Example: ``> kit.exe --publish omni.my.ext --publish-overwrite`` **Version must be specified** in a config file for publishing to succeed. All ``[package.target]`` config subfields if not specified are filled in automatically: * If the extension contains any ``[python]`` fields ``[package.target.python]`` is filled with current python version. * If the extension contains any ``[native]`` or ``[native.library]`` fields ``[package.target.platform]`` and ``[package.target.config]`` is filled with current platform information. An Extension package name will look like this: ``[ext_name]-[ext_tag]-[major].[minor].[patch]-[prerelase]+[build]``. Where * ``[ext_name]-[ext_tag]`` is the extension name (initially comes from extension folder). * ``[ext_tag]-[major].[minor].[patch]-[prerelase]`` if ``[package.version]`` field of a config. * ``[build]`` is composed from ``[package.target]]``. Pulling Extensions ================== There are multiple ways to get Extensions (both new Extensions and updated versions of existing Extensions) from a registry: 1. Use the UI provided by extension: `omni.kit.window.extensions` 2. If extensions specified in the app config file or required through dependencies are missing from local cache the system will attempt to sync with the registry and pull them. That means e.g if you have version "1.2.0" of an Extension locally it won't be updated to "1.2.1" automatically, because "1.2.0" satisfies the dependencies. To force an update run Kit with ``--update-exts`` flag: Example: ``> kit.exe --update-exts`` Pre-downloading Extensions ========================== You can also run Kit without starting any extensions. The benefit of this is that they will be downloaded and cached for the next run. To do that run Kit with ``--disable-ext-startup`` flag: Example: ``> kit.exe --disable-ext-startup`` Authentication and Users ========================== Omniverse Client library is used to all the operations with nucleus registry. Both syncing, downloading and publishing extensions requires signing in. To automate it separate 2 accounts can be explicitly provided to be used for read and write operations. Read and write user accounts can be set for ``omni.kit.registry.nucleus`` extension. Read user account is used for syncing with registry and downloading extensions. Write user account is used for publishing or unpublishing extensions. If no user is set it defaults to regular sign in using browser. By default kit comes with default read account set, to eliminate need for users to sign in to just pull extensions. Read user account setting: ``/exts/omni.kit.registry.nucleus/omniReadAuth`` Write user account setting: ``/exts/omni.kit.registry.nucleus/omniWriteAuth`` Format is: "user:pass", e.g. ``--/exts/omni.kit.registry.nucleus/omniReadAuth="bob:12345"``. Building Extensions ******************* Extensions are a runtime concept. This guide doesn't describe how to build them and how to build other extensions which may depend on a specific extension at build-time. One can use different tools and setups for that, we do however have some best practices. The Best source of information on that currently are: .. only:: internal * `Kit codebase `_ Default **Kit** is basically core SDK + collection of extensions. * `Kit extensions example repository `_ If you are going to develop extensions outside of the Kit repo this is the best example. You can just fork this repo, remove what you don't need and keep going from there. Start by reading its `README `_ The same way **each extension is compiled into one folder** we also aim to have **only one source folder** for each extension. Root ``premake5.lua`` file `includes each extensions `_ own small ``premake5.lua`` file which describes how to build it. `Premake public file `_ provides common functions and templates for different projects (**Carbonite** plugin, library, python module etc). All functions are documented and commented there, have a look. A good place to start might be the ``omni.example.hello`` extension (and other extensions). Copy and rename it to create a new extension. We also strive to use folder linking as much as possible. Meaning we don't copy python files and configs from the source folder to the target (build) folder, but link them. That allows changes to the files under version control to be immediately reflected even at runtime. Unfortunately we can't link files, because of Windows limitations, so folder linking is used. That adds some verbosity to the way the folder structure is organized. For example for simple python only extension, we link the whole folder: ``source/extensions/omni.example.hello/target`` -- [linked to] --> ``_build/windows-x86_64/debug/exts/omni.example.hello`` Folder named "target" gets linked automatically by ``project_ext()`` lua function. For an extension with binary components we link the config folder: ``source/extensions/omni.example.hello/config`` -- [linked to] --> ``_build/windows-x86_64/debug/exts/omni.example.hello/config`` And we can manually specify other parts to link in the premake file: ``repo_build.prebuild_link { "folder", ext.target_dir.."/folder" }`` When working with the build system it is always a good idea to look at what the final ``_build/windows-x86_64/debug/exts`` folder looks like, which folder links are there, where they point to, which files were copied there etc. Remember that the goal is to produce **one extension folder** which will then potentially will be zipped and published. Folder links are just zipped as is, as if they were actual folders. .. only:: internal References ********** .. note:: There is a `series of video tutorials on Kit Framework and Extensions `_ . .. note:: There is a `talk available on Extensions 2.0 `_ (look for "Kit Extensions 2.0").