xmake icon indicating copy to clipboard operation
xmake copied to clipboard

Allow to define user API for a rule

Open GooRoo opened this issue 4 months ago • 3 comments

Is your feature request related to a problem? Please describe.

The rules in their current state often rely on some values that should be set by a user, however, these values are not stated anywhere besides the actual rule's source code.

For example, when I use built-in qt.qmlplugin rule like this:

target("myextension")
    add_rules("qt.qmlplugin")
    add_files("src/Foo.cpp", "src/Foo.hpp")

then I always get an error:

error: QML plugin import name not set

The reason is that as a user, I must set a special variable like this:

target("myextension")
    add_rules("qt.qmlplugin")
    set_values("qt.qmlplugin.import_name", "MyExtension") -- 👈🏻 this one
    add_files("src/Foo.cpp", "src/Foo.hpp")

but there is no way for me to know that. The discoverability of this is essentially zero.

Or let's take Cap'n Proto as another example. As a user, I can pass capnp_rootdir via options, but again, there is no way for me to discover this.

So, essentially as a user, I would like a simpler (but somewhat stricter) way to configure and interact with my rules.

Describe the solution you'd like

Imagine I have a rule that is not bound to any file extensions. Let's assume, it takes a list of files and generates a text file with their names.

Then what I want as a user of this rule is to easily pass this list of files into the rule, maybe with some additional options. Also I probably want to be able to specify the resulting file name.

We could express it like this:

rule_property('files')
    set_list_type('glob')
    set_required(true)
    set_description('The list of files to be processed. Their names will be written into the output file.')

rule_property('output_name')
    set_type('string')
    set_default_value('out.txt')
    set_description('The output file name.')

rule_property('format')
    set_enum_values({'txt', 'md', 'html'})
    set_default_value('txt')
    set_description('The format of the output file.')

rule('text-gen')
    add_properties('files', 'output_name', 'format')

Now I could use it, for example, like this in my target:

target('myapp')
    local text_gen = add_rule('text-gen')
    text_gen.add_files('resources/**')
    text_gen.set_format('md')

If I forget to set/add some files for the rule, I can now get a concrete error:

error: 'text-gen' required property 'files' is not set:
    files : {glob, ...}
        The list of files to be processed. Their names will be written into the output file.

It should also be possible to create a command for getting some help on rules:

$ xmake rule text-gen
files : {glob, ...}
    The list of files to be processed. Their names will be written into the output file.

output_name : string
    The output file name.

format : 'txt' | 'md' | 'html'
    The format of the output file.

This could be extended further. For example, we could allow defining options for properties:

rule_property('files')
    set_list_type('glob')
    set_required(true)
    set_description('The list of files to be processed. Their names will be written into the output file.')
    add_option('prefix', 'string', {default = '/'})
    add_option('base_dir', 'string')

that would mean that in the target we could use it like this:

text_gen.add_files('resources/**', 'licenses/**', {prefix = '/mega', base_dir = 'blabla'})

This would also solve the current problem of inability to build DSLs when using packaged rules. For example, if I wrote my own rule qmlplugin (identical to the built-in one) and put it into the mega package, I will have to use some cumbersome syntax like this:

target('myapp')
    add_rules('@mega/qmlplugin')
    set_values('mega.qmlplugin.import_name', 'MyApp')

I cannot define a function (for the description scope) like this

function set_qmlplugin_name(name)
    set_values('mega.qmlplugin.import_name', name)
end

and then package it together with my custom rule, can I? This kills the whole idea of having rules extracted into packages.

But with the proposed syntax, the problem theoretically goes away since all the interaction with the rule is done through some local instance of the rule bound to the particular target. So maybe the interpreter may skip it at the first run till all the packages are installed and all the rules are resolved, I don't know.

Describe alternatives you've considered

See some initial thoughts in #6723.

Additional context

  • [ ] This should also work with packaged rules.
  • [ ] If my rule depends on another one, I should be able to set the properties for that rule from within my rule in the same way.

GooRoo avatar Aug 19 '25 14:08 GooRoo

but there is no way for me to know that. The discoverability of this is essentially zero.

I don't have time to write documentation, you can directly look at the rule source code

Allow to define user API for a rule

https://github.com/xmake-io/xmake/issues/4276 https://github.com/xmake-io/xmake/blob/dev/tests/apis/custom_scopeapis/xmake.lua

waruqi avatar Aug 19 '25 15:08 waruqi

@waruqi

I don't have time to write documentation, you can directly look at the rule source code

This is why I propose to make rules self-documenting.

GooRoo avatar Aug 19 '25 15:08 GooRoo

I also didn't have the time to think about and implement it. It would have involved a lot of changes, as well as API design.

In addition, your current design is limited to rules, but the attributes of these configuration values currently exist in all configuration scopes such as target/task/package/toolchain/rule and custom scope.

Even if the API is completed, it is not an easy task to implement it. It may take a long time and a lot of refactoring. But the little fragmented time I have every day is not enough for this.

waruqi avatar Aug 20 '25 01:08 waruqi