Skip to content

Conversation

@nickclark2016
Copy link
Member

@nickclark2016 nickclark2016 commented Feb 19, 2025

Problem Solved

Currently, there is no standardized way to handle exposing properties to dependent projects in Premake. Users have been required to build their own mechanisms for sharing properties or rely on third party modules, like premake-usage. This PR attempts to remedy this by providing a standardized solution to project dependency management in Premake ahead of the 5.0 stable release.

Proposed API

This PR uses the API implemented by premake-usage and extends it with CMake-like words of power. At a high level, a usage container defines a set of properties to be consumed at a later point, and a uses field consumes a set of usage blocks.

In the current implementation, a usage may only be defined from within the scope of a project when no filters are active. Similarly, a uses block may only be defined from within the scope of a project, but it may be applied while filters are active.

The following is an example of two projects with the proposed API:

project "A"
    defines { "A_PRIVATE_MACRO" }
    usage "MyUsage"
        defines { "A_MACRO_IN_A_USAGE" }

project "B"
    uses { "MyUsage" }

In the above example, A will only receive the A_PRIVATE_MACRO define, as A does not consume the MyUsage usage. Project B will only receive A_MACRO_IN_A_USAGE, as it only consumes the usage MyUsage. Properties defined at a project level are explicitly not inherited by the usage containing them.

CMake provides some extremely useful functionality in the form of their PRIVATE, PUBLIC, and INTERFACE keywords. This proposal and PR leverages these words of power in ways that match their meanings in CMake to make implemented Premake scripts based off of CMake easier.

  • PUBLIC - A usage block with this name will be applied to both the project defining the usage and any project consuming it via uses.
  • PRIVATE - A usage block with this name will only be applied to the project defining the usage and is inaccessible to other projects.
  • INTERFACE - A usage block with this name will only be applied to the project consuming the usage.

This slightly changes how we think of uses, as we could have many projects defining these blocks within the same Premake build. To access these blocks, scripts should specify the project name in the uses list.

project "A"
    usage "PUBLIC"
        defines { "A_PUBLIC" }
    usage "INTERFACE"
        defines { "A_INTERFACE" }
    usage "PRIVATE"
        defines { "A_PRIVATE" }

project "B"
    uses { "A" }

In this case, A has the A_PUBLIC and A_PRIVATE defines applied to it, while B has the A_PUBLIC and A_INTERFACE defines applied to it.

Transitive Dependencies

By default, dependencies consumed via uses are not applied transitively. However, this is quite trivial to enforce via PUBLIC usages. By specifying a uses field in the PUBLIC usage block, the usage can be applied to dependency chains indefinitely.

Other Notes

The uses field will not automatically apply links and dependson relationships between projects. In order to enforce this, authors of projects can utilize INTERFACE blocks to add these relationships like so:

project "A"
    usage "INTERFACE"
        depdendson { "A" }
        links { "A" }

For non-magic names, the behavior of uses is not well specified when there are multiple of the same name. There is further discussion in the Further Development section.

Known Limitations and Further Development

Token expansion is an area of concern. In the current iteration of this functionality, avoiding path tokens in custom build commands within usage blocks is recommended until that behavior can be agreed upon and refined.

The primary target of further development would be to allow "usage" blocks at any scope (global, workspace, group).

There is also room for improvements with usage resolution in uses. Currently, the behavior of uses is not well defined if there are multiple usage blocks with the same name (excluding the names of power). To resolve this, one of the following options may be viable (these are not mutually exclusive of each other and not an exhaustive list of options):

  • Warn or error out when multiple usage blocks with the same name are detected
  • Warn or error out when an ambiguous uses is applied
  • Provide a scoping operator, similar to C++'s operator::, to select a specific usage when there is ambiguity. (Example: :workspace:project:usage

@nickclark2016 nickclark2016 marked this pull request as ready for review February 20, 2025 17:59
@nickclark2016 nickclark2016 requested review from a team February 20, 2025 17:59
@nickclark2016 nickclark2016 force-pushed the feature/better-dependencies branch from c619cf2 to 04943ab Compare February 21, 2025 18:16
Copy link
Member

@samsinsane samsinsane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be worth having one of those generic docs, similar to Linking or Filters. This would give the opportunity to provide better examples of the intended usage without bloating the individual API docs with long examples.

@nickclark2016 nickclark2016 force-pushed the feature/better-dependencies branch from 8836603 to 7fa8361 Compare February 26, 2025 01:12
@nickclark2016 nickclark2016 force-pushed the feature/better-dependencies branch from 7fa8361 to 0b23f92 Compare March 2, 2025 23:08
@nickclark2016 nickclark2016 merged commit 62b9bc7 into premake:master Mar 2, 2025
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants