Skip to content

Proposed RFC Feature Engine, Project and Gem Versions #44

@AMZN-alexpete

Description

@AMZN-alexpete

⚠️ UPDATE 1/27/2023

Summary:

A versioning scheme is needed to determine code compatibility between engines, projects and gems. Currently, only the engine has a version scheme tied to 6 month releases which is not granular enough.

Version and dependency information will be added inside engine.json, project.json and gem.json and relevant tools will be updated to use this information. Version information will be incremented to indicate API or other relevant changes.

What is the relevance of this feature?

Versioning and dependency information will allow users and tools to make informed decisions regarding compatibility. Given the distributed nature of O3DE, an established version and dependency scheme is the only scalable way to allow de-centralized control over compatibility.

Feature design description:

The following changes will be made

  1. Semantic version fields in the format <major>.<minor>.<patch> (e.g. 1.21.9) will be added in engine.json, project.json and gem.json

    • <major> is for API-breaking changes
    • <minor> is for non-API-breaking changes that add new APIs or change them in a non-breaking way
    • <patch> is for all other non-API-breaking changes, usually important fixes
  2. Dependency fields containing lists of dependencies in the format <name><version specifier> (e.g. o3de >= 1.19.1) will be added in project.json and gem.json where version specifiers are compatible with PEP 440 so we can use existing Python versioning functionality. If a version specifier is omitted for a gem dependency, the project's engine version will be used to determine the latest compatible gem version. *IMPORTANT* Version specifiers indicate "known compatibility". This means, there isn't a way with the proposed versioning system to specify "known incompatibility". We use the version specifiers to recommend the most compatible gem, but we warn users when they attempt to use a version that is not listed as compatible.

  3. CMake will make #defines available with version information for compile-time compatibility control. e.g. EDITOR_VERSION_MAJOR, EDITOR_VERSION_MINOR, etc.

  4. The o3de.py CLI and Project Manager and CMake will take into account version and dependency specifications to display version information, determine compatibility and when a project needs to be re-compiled. The UX changes for these tools are not part of this RFC.

Workflows:

  1. During O3DE engine development in the development branch, the gem_version, engine_api_versions and engine_version will be updated as important changes are made.
    • When developers make a change to an API version in the engine or gem that ships with the engine, they will also update the engine_version. For example, if they change the minor version of a gem and zero out the patch version, they should also increase the minor version of the engine_version and zero out the patch version.
    • In the future, when a GitHub action exists to update the engine_version, developers will no longer need to manually update that field.
  2. When a stabilization branch is created in preparation for a release it should reflect version information that the main branch will have when stabilization is merged to main.
    • The engine_version minor version should be immediately incremented in development after creating the stabilization branch so there is less time that the two branches share the same engine_version value.
    • The engine_display_version in engine.json should be set to the appropriate YY.MM.XX release version prior to merging to main, preferably the last change submitted to the stabilization branch before merging to main to avoid accidentally merging this value back to development.
    • When merging from stabilization to development branches, developers should be careful not to bring the engine_display_version or engine_version from stabilization into development.
    • When merging changes from development to stabilization branches, developers should be careful to only include appropriate version changes. For example, if the major gem version was bumped in development but you're only merging over a patch fix for that gem into stabilization - do not bump the major gem version, just the patch.
  3. The major and minor engine versions in the stabilization branch should not change because only bug fixes should be merged to the branch which should never result in more than the patch version changing.
  4. When stabilization is merged to main it should have the correct engine_display_version.

Technical design description:

The versioning system will be backward compatible and optional. If there is no versioning or dependency information available the system will fallback to not enforcing compatibility, but will warn the user.

JSON file changes

engine.json is modified so we have version fields for release and development and a new engine_api_versions field is added so gems can depend on specific versions of APIs inside the core engine.

{
    "engine_display_version":"22.05.1",      // rename O3DEVersion field, set to 0.0.0 in development
    
    "engine_version":"1.0.1",    // use engine_version for all compatibility checks
 
    "engine_api_versions": {                 // versions of general APIs provided by the core engine (not gems)
        "editor":"3.1.4",
        "framework":"2.0.0",
        "launcher":"1.0.0",
        "tools":"4.0.0"
    },
    ...
}

project.json will now include a project and engine version and fields for engine and gem dependencies. Also, a compatible_engines field is added as a simple way for project maintainers to indicate known good versions of the engine (and gems) their project is compatible with.

{
    "project_version":"1.0.0",   // not needed for dependencies, but useful for users and added for consistency

    "engine_finder_cmake":"cmake/EngineFinder.cmake", // path to cmake script used to find the engine

    "engine":"o3de",             // engine_name this project was registered with
    "engine_version":"10.1.4",   // engine version this project was registered with
       
    "compatible_engines": [      // if empty (default) or missing, the project is assumed compatible with every engine
        "o3de>=1.0.0",           // project is compatible with any o3de engine greater than or equal to version 1.0.0
        "o3de-install==1.2.3"    // project is ALSO compatible with the o3de-install engine version 1.2.3
    ],
                               
    "engine_api_dependencies": [  // declaration of dependency on engine api versions, defaults to empty
        "framework~=2.0.0"
    ],
 
    "gem_dependencies": [        // rename "gem_names" to "gem dependencies" and support optional version specifiers
        "example~=2.3",          // project depends on example gem version 2.3.x
        "other==1.2.3",          // project ALSO depends on other gem version 1.2.3
        "lmbrcentral",           // if no version specifier, use latest version compatible with project's engine
        ...
    ],
    ...
}

⚠️ UPDATE 1/27/2023 project.json and user/project.json

  1. A new optional local-only file <project>/user/project.json can be used to override project.json settings locally. These properties can be set using o3de edit-project-properties --user. See O3DE CLI --user option changes for details.
  2. A new field engine_finder_cmake will be contain the relative path to the appropriate .cmake file that will be used to find the engine for the project. Currently this file is hardcoded to be cmake/EngineFinder.cmake but we need the flexibility to easily update and revert this logic for future engines and projects.
  3. A new field engine_path can be used to specify the path to the engine. The path may be local or relative if in user/project.json but may only be relative in the shared project.json. This field is provided for users to explicitly set the path to the engine, which is especially useful if they have multiple copies of an engine with the same name and version.

gem.json will now include a version field and fields for engine and gem dependencies. It will also have a compatible_engines field as a simple way for gem maintainers to indicate known good versions of the engine their gem is compatible with.

{
    "gem_version":"0.0.0",       // default gem version is 0.0.0
 
    "compatible_engines": [      // if empty (default) or missing, the gem is assumed compatible with every engine
        "o3de>=0.0.0"  ,         // gem is compatible with any version of an engine named o3de
        "o3de-sdk>=2.0.1, <=3.1.0"   // gem is ALSO compatible with o3de-sdk engine versions from 2.0.1 up to 3.1.0
    ],

    "engine_api_dependencies": [ // optional declaration of dependency on engine api versions, default is empty
        "framework~=2.0.0"
    ],
  
    "gem_dependencies": [        // rename "dependencies" to "gem_dependencies" and add version specifiers
        "AWSCore>=1.0.0"         // NEW version specifiers added
    ],
    ...
}

CLI tool changes

The o3de.py CLI tool will have the following updates:

  1. Whenever the user attempts to make a change that violates the dependency rules, the action will fail. A --force param can be used to force the action.
  2. Edit functionality for projects and gems will be updated to allow manipulation of the new fields
  3. Gem and project registration will take into account dependencies and their versions
  4. Enable/Disable gem functionality will take into account dependencies
  5. Newly enabled gems will appear in the project.json gem_dependencies field using a version specifier of
    1. no version specifier
      1. if the gem has no version information or
      2. is a gem that is shipped with the engine or
      3. the user selects the option to always use the latest gem that is compatible with their project's engine
    2. == <gem version> if the gem has version information and the user selects the option to use a specific gem version
  6. When gems are downloaded, the appropriate version will be downloaded and put in versioned folders.

⚠️ UPDATE 1/27/2023 o3de.py CLI changes

  1. A --user option will be added to register, edit-project-properties and enable-gem/disable-gem to use the user/project.json and manipulate it. If --user CLI operations fail the command does not fall back to just use the project.json.
  2. An upgrade command will be added that will be used to perform project upgrades. Initially this command will compare the project's engine version and the current engine version and execute Python commands to upgrade project files, outputting the list of files changed, where backups were stored, and letting the user know that, if they're using source control, they should check these files in.

Project Manager changes

The Project Manager tool will have the following updates:

  1. Version information will be shown. For engines with display version information, display the "engine_display_version" data, otherwise display the "engine_version".
  2. Project and Gem workflows will be updated to take into account version information and surface issues to the user.
    1. users will be able to select the version of a gem they want to use with their project, but the Project Manager will attempt to determine and recommend the most compatible gem
    2. appropriate UX (likely a warning) will be displayed when a user attempts to enable a gem or compile with a gem that may be incompatible with their engine or other gems
  3. When gems are downloaded, the appropriate version will be downloaded and put in versioned folders.

CMake changes

The following changes will be made to the CMake build scripts:

  1. CMake scripts will be updated to surface the version information from each .json file as #defines that can be used by code in one of the following formats:

    ENGINE_VERSION_<MAJOR/MINOR/PATCH>
    ENGINE_<engine API>_API_VERSION_<MAJOR/MINOR/PATCH>
    <PROJECT/GEM>_<project name/gem name>_VERSION_<MAJOR/MINOR/PATCH>

    • Example 1: a gem named example with version 1.2.3 would have the following defines made available:
    GEM_EXAMPLE_VERSION_MAJOR 1
    GEM_EXAMPLE_VERSION_MINOR 2
    GEM_EXAMPLE_VERSION_PATCH 3
    • Example 2: a core engine API named framework with version 2.3.0 would have the following defines:
    ENGINE_FRAMEWORK_API_VERSION_MAJOR 2
    ENGINE_FRAMEWORK_API_VERSION_MINOR 3
    ENGINE_FRAMEWORK_API_VERSION_PATCH 0
  2. CMake scripts will be updated to take into account gem versions as well as gem names when determining correct sub-directories

⚠️ UPDATE 1/27/2023 EngineFinder CMake changes

  1. The CMakeList.txt in projects will be updated to read the project.json and then the user/project.json for the "engine_finder_cmake" entry and then include() that path.
  2. The EngineFinder.cmake file will be updated to check project <-> engine compatibility.

Content updates

  1. Gems provided with the engine will be updated with default versions 1.0.0
  2. AutomatedTesting and other sample projects will be updated with default versions 1.0.0
  3. Gem and project templates will be updated with the new fields.
  4. Gem providers will be notified so they can update their projects and gems.
  5. Documentation will be updated to reflect the version changes.

What are the advantages of the feature?

  1. Provides more granular versioning beyond individual releases
  2. Deters (but does not prevent) users from accidentally enabling incompatible gems in their projects, or incompatible projects in their engines.
  3. Allows gem creators to specify ranges of engine versions or engine library versions that gems are compatible with
  4. Allows projects to be incrementally upgraded in development branches instead of per engine release version

What are the disadvantages of the feature?

  • Maintaining version numbers is now a developer burden. Developers must understand when to update engine and gem versions and dependency information.
  • Old engines have the O3DEVersion field in engine.json that may cause confusion while those engines are in use.

How will this be implemented or integrated into the O3DE environment?

This does not require any new libraries. It will expand on both C++ source code and o3de python CLI code.

Are there any alternatives to this feature?

Two other similar approaches were considered but not selected due to their reliance on git and not all customers will use git as source control.

Storing the version information in .h (header) files does not satisfy the requirement of being able to determine version information when the source isn't available.

  1. GitHub Tags
    Utilize the git tag system to mark commits that increment version.

    Pros:

    • Does not require editing a file

    Cons:

    • Requires access to git api from scripts or a minimal clone of the repo with the tags
    • Per repo, so the tags must be configured when merging from a fork and will be different
    • May require some form of approval to create new tags for a version
    • This versioning only works for customers using git for source control
  2. Version Per Commit
    Each commit is considered a separate incremental version.

    Pros:

    • Automatic versioning
    • Does not require editing a file

    Cons:

    • Requires access to git API from scripts checking this
    • Commits can be squashed or merged from other branches changing hashes so they are not stable until in development
    • This versioning only works for customers using git for source control
    • Customers will have to use a version specifier to depend on a commit hash which isn't numerical for readability or for discovering where the engine is at in development.
      For example, a customer would need to depend on commit >=<sha1 hash> but o3de>=23e38520e9f could be after commit hash o3de>=a086bcbe0

How will users learn this feature?

  1. The version of the engine along with the current version of projects as well as the supported versions for a gem will all be displayed within the Project Manager for users to see.
  2. Upgrading projects will be added as a feature utilizing this system and automatically detecting potential upgrades and presenting them to the user.
  3. Engine, project and gem version fields will be visible in the Project Manager and in the o3de CLI.
  4. Documentation will be updated for developers to know more about how to use the versioning system.

Are there any open questions?

  1. What are the best defaults to use for project and gem versions, would something be better than 1.0.0?
  2. What are the best version specifiers to use for gem_dependencies when enabling a gem in a project?
  3. What is the best way to update the engine_version?
  4. What are the best API groupings to use for the engine_api_versions list - are there better ones than those proposed?

Examples

Project using latest version of gems for an engine

When you intend to use the latest versions of gems that are compatible with whatever engine your project uses, you would leave the version specifier portion of the gem_depencencies blank and provide an engine_name and engine_version. This will likely be common for teams that use the pre-built SDK.
The following example project will attempt to use the latest version of PopcornFX and KytheraAI gems that are compatible with the o3de-sdk engine with version 1.2.3, and because the LmbrCentral gem is provided with the engine, no version specifier is necessary.

project.json

{
    "project_name":"Example",
    "engine_name":"o3de-sdk",
    "engine_version":"1.2.3",
    "gem_dependencies": [
        "LmbrCentral",
        "PopcornFX",
        "KytheraAI",
    ],
}

Project requiring specific gem versions

Game studios with engineers dedicated to engine integrations may prefer to download all the necessary gems for a project and check them into source control and specify the exact version to use to discourage non-engineers from using other versions that may not be approved.
In this example, the project specifies a custom internal engine and version, and the exact versions of the PopcornFX and KytheraAI gems to use. The LmbrCentral still does not need a version specifier because it is always provided with the engine, but the team could provide a version specifier if desired for consistency.

project.json

{
    "project_name":"Example",
    "engine_name":"o3de-internal",
    "engine_version":"1.0.1",
    "gem_dependencies": [
        "LmbrCentral",
        "PopcornFX==1.2.3",
        "KytheraAI==2.3.4",
    ],
}

Gem compatible with engine release versions

When you intend to provide a gem that only needs to be compatible with released engine versions you can specify those exact versions in the compatible_engines field. You will not be able to use a range because that would include development engine versions.
The following example declares this gem is compatible with engines version 1.2.3 and 2.3.4
Gems\Example\1.2.3\gem.json

{
    "gem_name":"Example",
    "compatible_engines": [
        "o3de-sdk == 1.2.3",
        "o3de-sdk == 2.3.4",
        "o3de == 1.2.3",
        "o3de == 2.3.4"
    ],
}

Gem depending on specific APIs

When you intend to provide a gem that should be compatible with any future engine so long as a core API doesn't change you can use the engine_api_dependencies and gem_dependencies fields .
The following example shows a gem that should be compatible with all versions of the engine containing the framework API major version 2
Gems\Example\1.2.3\gem.json

{
    "gem_name":"Example",
    "engine_api_dependencies": [
        "framework ~= 2.0.0"
    ],
}

Metadata

Metadata

Assignees

Labels

kind/roadmapCategorizes an issue that goes in the O3DE Public Roadmapmtg-agendaTag for meeting proposed and accepted agendasrfc-featureRequest for Comments for a Feature

Type

No type

Projects

Status

📋 Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions