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

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

### Example: Markdown to HTML

```lua
-- 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()

```lua
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

```lua
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

```lua
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)

```lua
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)

```lua
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:

```lua
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

```lua
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

```lua
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

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

### Adding Import Modules

```lua
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

```lua
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

```lua
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

```lua
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}

- Complete API documentation: [Custom Rule API](/api/description/custom-rule)
- Built-in rules reference: [Built-in Rules](/api/description/builtin-rules)
- Rule examples: [Rule Examples](/examples/cpp/protobuf) 