o3de / sig-core

5 stars 6 forks source link

Proposed RFC Feature Engine, Project and Gem Versions #44

Open AMZN-alexpete opened 2 years ago

AMZN-alexpete commented 2 years ago

:warning: 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
  1. 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.

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

  3. 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.
      1. 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.
      2. 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
        ...
    ],
    ...
}

:warning: 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.

:warning: 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

:warning: 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"
    ],
}
AMZN-Gene commented 2 years ago

Relates to #https://github.com/o3de/o3de/issues/9669 This will be nice for detecting server and client mismatch.

  1. I see the patch number changing quite often, like on a per PR level. As any new feature or bug fix goes into the build, it might be one that really matters when it comes to client/server being on the same page. Will devs remember to increase the version with each check-in? Can patch # be automatically incremented if a PR didn't ship with a major or minor change?

  2. I wonder if patch numbers will be quickly meaningful upon seeing it. Suppose I see the patch number is different on both client and server, I'll likely still have to go and look in git to see if a particular change I want/need falls into the patch number. More often than not, the date of a build is quicker to digest. Example: "Oh, this build is from yesterday, and I definitely remember hearing about that fix last week, so I'm pretty sure I have the fix".

I sort of want a way to automatically update a file in git to store and lookup the last commit timestamp. Then in my client and server I can see major.minor.patch + timestamp. If I notice the time stamps on client and server are a week apart vs 1 month apart, I can start getting suspicious that I'm running two incompatible builds should there be any weird behavior.

AMZN-alexpete commented 2 years ago

@AMZN-Gene, re: using this for server/client mismatch, @kberg-amzn has some thoughts on why network or client/server versioning should not be tied to engine, project or gem versioning, let me know if that changes your feedback.

re: patch numbers changing often - that is certainly a possibility, but I would discourage bumping the patch number with every bug fix, minor feature or chore and only recommend patch changes for ones that are significant enough that a dependency needs to know about it - like security fixes.

fabioanderegg commented 2 years ago

Sounds like a good approach, I especially like the decision to use semver and the Python versioning scheme.

A few questions, more for understanding the proposal than real feedback:

I am wondering if it's even necessary/recommended to set compatible_engines, when gem_dependencies and engine_api_dependencies are setup carefully?

AMZN-alexpete commented 2 years ago

Thanks for the feedback @fabioanderegg,

  • There are examples for compatible_engines with values "o3de", "o3de-install", "o3de-sdk". What are the differences between those?

The name of the engine in engine.json is different in a couple places.

  1. In GitHub in the development and main branch the name is o3de
  2. In the Installer SDK the name is changed to o3de-sdk
  3. In our documentation where we describe creating your own SDK we use the engine name o3de-install

From what I understand, "o3de" applies to everything, and the second line in the project.json examples doesn't do anything? What is the use-case for those other lines? What should we (Kythera) set?

The engine field in project.json is set to the engine name of whatever engine the user registered the project with.
My recommendation for what you use in your sample project.json and gem.json depends on how often you intend to release updates and how many different versions you are going to support.

  • The main issue we are dealing with is support for the development branch. From what I understand, this is nicely solved by setting gem_dependencies (e.g. for PhysX, which would be something like "physx~=2.1") and engine_api_dependencies in a similar way.

Yes, adding support for gem_dependencies and engine_api_dependencies should allow you to better indicate to users what versions of the development branch your project and gems have been tested with.

I am wondering if it's even necessary/recommended to set compatible_engines, when gem_dependencies and engine_api_dependencies are setup carefully?

In some cases you must use compatible_engines to support older release engines that don't have gem or engine versions. It is also useful for more casual sample project and gem providers who are mainly targeting released versions of O3DE and will not be able to actively support development.

ValPKFX commented 2 years ago

In your example, the development version is not greater that the release version.

    "engine_release_version":"22.05.1",      // rename O3DEVersion field, set to 0.0.0 in development
    "engine_development_version":"1.0.1",    // use dev version when engine_release_version is 0.0.0, empty or missing

Will it be the case? If that so, we will probably need a #define added by CMake to determine if we are on the development version. Because right now it seems the ENGINE_VERSION_<MAJOR/MINOR/PATCH> will take either the value of engine_release_version or engine_development_version but without an additional define to tell if we are on the development version, it will be hard to make a gem compatible with both versions.

And in most of the cases, when handling multiple release versions in the code, the code of the latest release will need to be activated for the development version too. You can see an example here : https://github.com/PopcornFX/O3DEPopcornFXPlugin/blob/9ba771490535ab5f4f61d65ac1ba7024cb535db7/Code/Source/Integration/Render/AtomIntegration/PopcornFXFeatureProcessor.cpp#L133

loherangrin commented 2 years ago

I like the proposal and especially the tracking of fine-grained features with engine_api_versions and gem_dependencies.

I have one concern about using the engine name incompatible_engines for gem.json, since it is a user-editable value and it may become misleading for gem developers. I provide an example to explain better what I mean: a developer defines that the gem is compatible with o3de>=22.05.00, which is the simplest solution to avoid listing any specific API version (and thus, the one that could be used often for casual purposes). If I didn't misunderstand the proposal, this makes the gem incompatible with the pre-built installer (using o3de-sdk) or any other custom installation of the same engine (using any alias name in the docs steps).

Although the first case is simple to fix (i.e. a developer must always define o3de AND o3de-sdk, and it could be written in the documentation or pre-filled by CLI), the second one it's more difficult to address from the point of view of the developer, becoming responsibility of users that use aliases to adjust gems to their engine installations.

I was thinking about the following suggestion, starting from the assumption that engine variants (e.g. source, sdk) with the same version should not provide different list of features if they are released by the same entity (e.g. O3DE, a game studio):

This allows users to customize their engine installation names (even if targeting the same engine version) without loosing the compatibility check for gems, whereas developers haven't to explicit all engine APIs if they don't want.

AMZN-alexpete commented 2 years ago

@ValPKFX Thank you for your feedback

Because right now it seems the ENGINE_VERSION_<MAJOR/MINOR/PATCH> will take either the value of engine_release_version or engine_development_version but without an additional define to tell if we are on the development version, it will be hard to make a gem compatible with both versions.

Thanks for pointing that out, I'll update the design to include ENGINE_RELEASE_VERSION_MAJOR/MINOR/PATCH and ENGINE_DEVELOPMENT_VERSION_MAJOR/MINOR/PATCH to distinguish between development and releases. Perhaps some day in the future we can combine them somehow, but for now the release version is based on the release date, but in development versions are based on API changes.

@loherangrin thanks for pointing out the issue surrounding multiple engine names/versions.

If I didn't misunderstand the proposal, this makes the gem incompatible with the pre-built installer (using o3de-sdk) or any other custom installation of the same engine (using any alias name in the docs steps).

The compatible_engines field only tells us what engines are known to work with a gem (or project). If an engine is not in the compatible_engines list it does not mean that the gem is incompatible, just that it isn't known to be compatible. It'd be a little bold for a developer to suggest that their gem is forward compatible with all future unreleased versions of the engine, but I'm sure it will happen, so it is good to consider it :smiley:

In your example, if a developer wanted to indicate their gem was compatible with any engine, they could leave the compatible_engines field empty. If they wanted to state that their gem is compatible with any engine past a certain known version of o3de then the best they could do with this new scheme would be to add o3de>=1.2.3 and o3de-sdk>=22.05.01 to compatible_engines. I think if we wanted to let developers indicate that their custom SDK provides the same functionality as another engine, maybe we could support a compatible_engines alias in the engine.json like this

{
    "engine_name" : "o3de-custom",
    "engine_release_version" : "A.B.C",
    "engine_development_version" : "1.2.3",
    "compatible_engines": [
        "o3de-sdk>=22.05.01",            <-- this SDK provides the same functionality as o3de-sdk >= 22.05.01
        "o3de==1.2.3"                    <-- this SDK provides the same functionality as o3de 1.2.34
    ],
    ...

Thoughts?

As you pointed out, if a gem developer wanted to make their gem compatible with multiple engine distributions from various providers (O3DE, game studios etc) they should use engine_api_versions and gem_dependencies because they are trying to support a wide variety of engines they have likely not tested.

That said, we will always let the user use any combination of engine and gems they want, but steer them toward compatible versions if there are any available, and otherwise point out to them when a gem/engine combination isn't known to be compatible.

I was thinking about the following suggestion, starting from the assumption that engine variants (e.g. source, sdk) with the same version should not provide different list of features if they are released by the same entity (e.g. O3DE, a game studio):

  • split the engine distribution name (o3de) from its installation one (o3de-src, o3de-sdk, my-pr-for-o3de, etc.), using the former to check the version compatibility instead of the latter. The current behavior for registering different engine variants in O3DE manifesto and retrieving them by binaries can be kept the same using the latter value as-is.

So if I understand correctly, the engine.json would contain a new field (engine_variant) like this?

{
    "engine_name" : "o3de",
    "engine_variant" : "src/sdk/my-pr-for-o3de",
    "engine_evelopment_version" : "1.2.3",
    ...

Changing what the engine_name means is probably outside the scope of this RFC, but worth considering. We could probably keep the engine_name field the same and add a new field for engine-engine similarity/compatibility to accomplish the same thing.

I think it would be good to say the engine and gems provided in a source and SDK variant of the engine are the same and they can be treated the same for version compatibility checks, but we can only guarantee that for the O3DE distributions.

Our documentation shows how to make an SDK and install it without making any changes to the provided Gems or engine, but I think the goal of making a custom SDK is to make changes for distributing and so in most cases users who make a custom SDK will have functionality that is different from what O3DE provides. That said, I'm sure you're right that they'll want to make it easy for their engine to be listed as compatible with gems that are known compatible with another engine.

loherangrin commented 2 years ago

If an engine is not in the compatible_engines list it does not mean that the gem is incompatible, just that it isn't known to be compatible

Thank @AMZN-alexpete for the explanation, I got the specification in the most strict way. So, when the compatibility status is unknown, is the user allowed to continue anyway showing some additional warning message (or nothing at all)?

I think if we wanted to let developers indicate that their custom SDK provides the same functionality as another engine, maybe we could support a compatible_engines alias in the engine.json like this [...] Thoughts?

It looks good to me, assuming that an alias cannot be expanded recursively (e.g. if my-engine is compatible with your o3de-custom==1.2.3, it doesn't inherit your two engine compatible definitions o3de==1.2.3 and o3de-sdk>=22.05.01).

So if I understand correctly, the engine.json would contain a new field (engine_variant) like this?

Sorry for not having added an example. My proposal was limited to add a new field in engine.json to identify who is releasing the engine, in order to minimize the impact on the current meaning of the engine_name property. Something like:

{
    "engine_name" : "o3de-sdk",                 # or, o3de-src, my-pr-for-o3de, ...
    "engine_release_version" : "1.2.3",
    "engine_distribution" : "o3de",              # or, custom-engine, game-studio, ...

O3DE releases can keep the two different names in engine_name (o3de and o3de-sdk), but use a single o3de value for engine_distribution (or any other similar term, like engine_vendor, engine_owner, engine_type). When looking for compatibility, the pair <engine_distribution>:<engine_release_version> is used instead of <engine_name>:<engine_release_version>.

However, your previous addition of compatible_engines may already supersede this one.

AMZN-alexpete commented 2 years ago

So, when the compatibility status is unknown, is the user allowed to continue anyway showing some additional warning message (or nothing at all)?

Hi @loherangrin! A user will always be allowed to continue and add a gem even if it is not known to be compatible, but there will be some kind of visual indication to the user that the gem is not known compatible.

Thank you for providing an example of engine_distribution use. I'm still thinking about it, but my initial thought is that currently the engine_name is used for all the important stuff like registration, and it might be confusing to users not to use the name in the dependency field, however it is nice that an engine_distribution field would give a way for users to say multiple engines that are packaged differently are compatible and come from the same distribution source.

AMZN-alexpete commented 1 year ago

@ValPKFX @fabioanderegg during the sig-core weekly meeting today we discussed how we could simplify the engine version fields and thought it best to make the following changes:

  1. engine_development_version rename to engine_version - this will be the API version for the engine in any branch (development, release, stabilization etc). There are a few scenarios where we could get into a situation where the engine in two branches have the same engine version but have different code, however, the APIs should be compatible.
  2. engine_release_version rename to engine_display_version - this is really a branding name with an arbitrary version scheme that we shouldn't use for version compatibility checks because it is not tied to any API versions in the engine. It will be used for marketing purposes and as a user friendly version, but not in the compatible_engines field.

@loherangrin we discussed the engine_distribution field and think it's best to not add that field at this time, and instead add it at a later date or add the compatible_engines field to the engine.json file at a later date when we have more alternate distributions of the engine and can discuss with those owners what is the best solution.

tkothadev commented 1 year ago

@AMZN-alexpete Hey Alex, I think I agree with these changes over all. Trying to wrangle the display_version in the actual compatibility logic would be too complex, especially since the nature of our release builds means these two version schemes are fundamentally asynchronous.

That being said, although the actual compatibility logic should be only tied to the engine_version, do we also want to consider a mapping scheme (even hardcoded) between display_version to one or more engine_versions? From an API perspective we could include a helper function API for something roughly like List<Version> getEngineVersion( input displayVersion) where the list returns either one, or more engine versions (I suspect we should strive to keep a 1:1 mapping, but I'm not aware if we have quirks where we have a single display version map over a range of engine versions)

AMZN-alexpete commented 1 year ago

..., do we also want to consider a mapping scheme (even hardcoded) between display_version to one or more engine_versions?

We certainly could have a helper function to return the engine version based on the display version - though maintaining a map may not be necessary because it's unlikely users will have many engines installed on their machine at a time. A simple way for users to discover the engine version would be for us to add that information to the Project Manager's Engine screen.

tkothadev commented 1 year ago

That's true, that would definitely be simpler. Depending on how we do it though, I would be concerned about it being error-prone with typos for a given engine screen.

A mapping structure can help us with validation, but if the use case is too simple it likely doesn't justify the effort into making that structure.

lemonade-dm commented 1 year ago

For the Feature Design Description section, step 4 mentions that o3de.py and Project Manager will need to be updated to handle the version specifications

The o3de.py CLI and Project Manager 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.

CMake should also be mentioned in that entry.

For the workflows sections, it says that the engine_version patch field should be incremented, but I think that should be the engine version minor field.

Pretty much I imagine before the the stabilization branch is created the engine.json has the engine major and minor versions for the next release. i.e

development branch engine.json

"engine_version": "1.1.0"

When the stabilization branch is created they both have an engine_version of "1.1.0".

development branch engine.json

"engine_version": "1.1.0"

stabilization branch engine.json

"engine_version": "1.1.0"

But right after the stabilization branch is created a commit can be made to the development to update the minor version to 1.2.0

development branch engine.json

"engine_version": "1.2.0"

stabilization branch engine.json

"engine_version": "1.1.0"

Afterwards the workflow could specify that the the major and minor versions should NEVER be updated in the stabilization branch. In the example, the the stabilization branch is only allowed to have versions 1.1.0 thru 1.1.x While the development branch right after stabilization is cut will have versions 1.2.0 and above.

Now I agree with making the engine display version just a user friendly version for marketing purposes Now on the topic I don't think we should try to make a mapping structure of engine_display_version to engine_version just yet.

I think we should first try just using the engine_version field and see how that works. And then if there are any customer request, we can decide then how to handle it.

I think it adds complexity that is not needed and can potentially be a flaw in the version system if we allow it, so I think we should let some time pass to handle it.

Furthermore users wanting to specify a dependency on a specific version would have to look in the engine.json determine the version , which has both fields anyway. So it is not like the user can have the excuse to say they only know the "marketing" version and not the actual version.

Finally the version information isn't targeting for novice users. It requires at least the ability to locate the "engine_version" field in the engine.json file.

AMZN-alexpete commented 1 year ago

@lumberyard-employee-dm Thanks for the feedback - I agree, a minor version update would be better than a patch change. I've made that change and updated the Feature Design Description to also mention CMake.

lemonade-dm commented 1 year ago

This RFC looks good with me and I am OK with signing off with the plan to implement this semantic version scheme as well as the mechanism for specifying dependencies on engine api versions, engine version and gem versions.

AMZN-alexpete commented 1 year ago

One consideration that was brought up was - do we need template.json support for version, compatible_engines and engine_api_dependencies or can we rely on the project.json or gem.json within each template to provide that info? My thought is - we go with the version information inside the project.json or gem.json information. This is simpler and easier to maintain.

flappybirdace commented 1 year ago

How would non-official/custom engine versioning work? One would want to fork the engine for their own work without conflating custom engine semver with official engine semver. In the gem world one could simply give the gem a new name to fork it. But in the engine world there is simply 'the engine' with a version without scope for renaming. One way to do this would be to prepend the engine versioning with a name that identifies the engine as 'official' or something user defined.

AMZN-alexpete commented 1 year ago

How would non-official/custom engine versioning work? One would want to fork the engine for their own work without conflating custom engine semver with official engine semver. In the gem world one could simply give the gem a new name to fork it. But in the engine world there is simply 'the engine' with a version without scope for renaming. One way to do this would be to prepend the engine versioning with a name that identifies the engine as 'official' or something user defined.

The engine for a non-official/custom engine.json would reflect the name of the custom engine (e.g. myengine) and the engine_version field would reflect the version of that custom engine. If a gem wanted to specify it was compatible with version 1.2.3 of myengine it could have an compatible_engines entry that looked like this:

"myengine==1.2.3"

A common example of this would be game teams that have a custom internal version of the engine. As soon as that game team starts to change the engine version to reflect changes they made to the engine, they would now be responsible for determining what gems are compatible with their engine and updating those gem.json files to indicate compatibility.

@flappybirdace Did I understand your question correctly?

flappybirdace commented 1 year ago

How would non-official/custom engine versioning work? One would want to fork the engine for their own work without conflating custom engine semver with official engine semver. In the gem world one could simply give the gem a new name to fork it. But in the engine world there is simply 'the engine' with a version without scope for renaming. One way to do this would be to prepend the engine versioning with a name that identifies the engine as 'official' or something user defined.

The engine for a non-official/custom engine.json would reflect the name of the custom engine (e.g. myengine) and the engine_version field would reflect the version of that custom engine. If a gem wanted to specify it was compatible with version 1.2.3 of myengine it could have an compatible_engines entry that looked like this:

"myengine==1.2.3"

A common example of this would be game teams that have a custom internal version of the engine. As soon as that game team starts to change the engine version to reflect changes they made to the engine, they would now be responsible for determining what gems are compatible with their engine and updating those gem.json files to indicate compatibility.

@flappybirdace Did I understand your question correctly?

Yes. My bad, I didn't read the conversation correctly.

lemonade-dm commented 1 year ago

A new field engine_path can be used to specify the (local or relative) path to the engine. This field is provided for users to explicitly set the path to the engine locally, which is especially useful if they have multiple copies of an engine with the same name and version.

@AMZN-alexpete It should be mentioned that the "engien_path" field is only read from the user/project.json file, it is not part of the shared and potentially source controlled project.json file. The issue would be adding machine specific paths that would cause break other users.

AMZN-alexpete commented 1 year ago

@AMZN-alexpete It should be mentioned that the "engine_path" field is only read from the user/project.json file, it is not part of the shared and potentially source controlled project.json file. The issue would be adding machine specific paths that would cause break other users.

@lumberyard-employee-dm I was actually just thinking of a common use-case where it might be good to support relative paths for the "engine_path" field in project.json for teams that distribute the engine + projects + other files in a common folder structure via source control. They could put "engine_path":"../o3de" in their project.json. For example, this kind of layout:

/source-control-root
     /o3de  (the engine)
     /project (the project)
     ... (other folders)

Thoughts?

lemonade-dm commented 1 year ago

The problem with that approach is that an individual user would have no way to override the engine using the "engine" key without modifying the project.json file itself which is against the entire purpose of having a user/project.json.

Let's say as a common team user, they would like to use "custom-user-o3de" via the "engine" key to debug the project against an older version of the engine. Theu specify that key in their user/project.json file, but because of the "engine_path" key in the project.json, their registered engine would be ignored.

AMZN-alexpete commented 1 year ago

Let's say as a common team user, they would like to use "custom-user-o3de" via the "engine" key to debug the project against an older version of the engine. The specify that key in their user/project.json file, well because of the "engine_path" key in the project.json, their registered engine would be ignored.

I see what you mean. One option would be for them to unset the "engine_path" var by setting it to "" in their user/project.json, and another would be for them to provide an actual path to the engine they are trying to target in "engine_path" on their local machine. But, I'd rather not make things any more complicated than they are while supporting the most common use cases, so I'm OK for now with ignoring "engine_path" in project.json - users can customize their engine finder CMake or require their users to run a custom script to register the engine to accomplish the same thing.