custom-rule.md 7.5 KB


outline: deep

Custom Rules {#custom-rule}

Xmake not only natively supports multi-language file building, but also allows users to implement complex unknown file building through custom build rules. Custom rules let you define specialized build logic for specific file types.

Basic Concepts {#basic-concepts}

Custom build rules are defined using the rule() function and associate a set of file extensions to rules through set_extensions(). Once these extensions are associated with rules, calls to add_files() will automatically use this custom rule.

Creating Simple Rules {#create-simple-rule}

Basic Syntax

rule("rulename")
    set_extensions(".ext1", ".ext2")
    on_build_file(function (target, sourcefile, opt)
        -- build logic
    end)

Example: Markdown to HTML

-- Define a build rule for markdown files
rule("markdown")
    set_extensions(".md", ".markdown")
    on_build_file(function (target, sourcefile, opt)
        import("core.project.depend")
        
        -- make sure build directory exists
        os.mkdir(target:targetdir())
        
        -- replace .md with .html
        local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
        
        -- only rebuild if file has changed
        depend.on_changed(function ()
            -- call pandoc to convert markdown to html
            os.vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
        end, {files = sourcefile})
    end)

target("test")
    set_kind("object")
    add_rules("markdown")
    add_files("src/*.md")

Applying Rules to Targets {#apply-rules-to-target}

Method 1: Using add_rules()

target("test")
    set_kind("binary")
    add_rules("markdown")  -- apply markdown rule
    add_files("src/*.md")  -- automatically use markdown rule

Method 2: Specifying in add_files

target("test")
    set_kind("binary")
    add_files("src/*.md", {rules = "markdown"})  -- specify rule for specific files

::: tip Note Rules specified via add_files("*.md", {rules = "markdown"}) have higher priority than rules set via add_rules("markdown"). :::

Rule Lifecycle {#rule-lifecycle}

Custom rules support the complete build lifecycle and can execute custom logic at different stages:

Main Stages

  • on_load: Executed when rule is loaded
  • on_config: Executed after configuration is complete
  • before_build: Executed before building
  • on_build: Executed during building (overrides default build behavior)
  • after_build: Executed after building
  • on_clean: Executed during cleaning
  • on_package: Executed during packaging
  • on_install: Executed during installation

Example: Complete Lifecycle

rule("custom")
    set_extensions(".custom")
    
    on_load(function (target)
        -- configuration when rule is loaded
        target:add("defines", "CUSTOM_RULE")
    end)
    
    before_build(function (target)
        -- preparation work before building
        print("Preparing to build custom files...")
    end)
    
    on_build_file(function (target, sourcefile, opt)
        -- process individual source files
        print("Building file:", sourcefile)
    end)
    
    after_build(function (target)
        -- cleanup work after building
        print("Custom build completed")
    end)

File Processing Methods {#file-processing-methods}

Single File Processing (on_build_file)

rule("single")
    set_extensions(".single")
    on_build_file(function (target, sourcefile, opt)
        -- process single file
        local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".out")
        os.cp(sourcefile, targetfile)
    end)

Batch File Processing (on_build_files)

rule("batch")
    set_extensions(".batch")
    on_build_files(function (target, sourcebatch, opt)
        -- batch process multiple files
        for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
            print("Processing file:", sourcefile)
        end
    end)

Batch Command Mode {#batch-command-mode}

Using on_buildcmd_file and on_buildcmd_files can generate batch commands instead of directly executing builds:

rule("markdown")
    set_extensions(".md", ".markdown")
    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
        -- ensure build directory exists
        batchcmds:mkdir(target:targetdir())
        
        -- generate target file path
        local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
        
        -- add pandoc command
        batchcmds:vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
        
        -- add dependency files
        batchcmds:add_depfiles(sourcefile)
    end)

Rule Dependencies {#rule-dependencies}

Adding Rule Dependencies

rule("foo")
    add_deps("bar")  -- foo depends on bar rule

rule("bar")
    set_extensions(".bar")
    on_build_file(function (target, sourcefile, opt)
        -- bar rule build logic
    end)

Controlling Execution Order

rule("foo")
    add_deps("bar", {order = true})  -- ensure bar executes before foo
    on_build_file(function (target, sourcefile, opt)
        -- foo rule build logic
    end)

Common Interfaces {#common-interfaces}

Setting File Extensions

rule("myrule")
    set_extensions(".ext1", ".ext2", ".ext3")

Adding Import Modules

rule("myrule")
    add_imports("core.project.depend", "utils.progress")
    on_build_file(function (target, sourcefile, opt)
        -- can directly use depend and progress modules
    end)

Getting Build Information

rule("myrule")
    on_build_file(function (target, sourcefile, opt)
        print("Target name:", target:name())
        print("Source file:", sourcefile)
        print("Build progress:", opt.progress)
        print("Target directory:", target:targetdir())
    end)

Practical Examples {#practical-examples}

Example 1: Resource File Processing

rule("resource")
    set_extensions(".rc", ".res")
    on_build_file(function (target, sourcefile, opt)
        import("core.project.depend")
        
        local targetfile = target:objectfile(sourcefile)
        depend.on_changed(function ()
            os.vrunv("windres", {sourcefile, "-o", targetfile})
        end, {files = sourcefile})
    end)

Example 2: Protocol Buffer Compilation

rule("protobuf")
    set_extensions(".proto")
    on_build_file(function (target, sourcefile, opt)
        import("core.project.depend")
        
        local targetfile = path.join(target:autogendir(), path.basename(sourcefile) .. ".pb.cc")
        depend.on_changed(function ()
            os.vrunv("protoc", {"--cpp_out=" .. target:autogendir(), sourcefile})
        end, {files = sourcefile})
        
        -- add generated file to target
        target:add("files", targetfile)
    end)

Best Practices {#best-practices}

  1. Use Dependency Checking: Avoid unnecessary rebuilds through depend.on_changed()
  2. Error Handling: Add appropriate error handling logic in rules
  3. Progress Display: Use opt.progress to display build progress
  4. Modularization: Break complex rules into multiple simple rules
  5. Documentation: Add clear comments and documentation for custom rules

More Information {#more-information}