Browse Source

add custom rule and toolchain

ruki 6 months ago
parent
commit
dca6cfda80

+ 629 - 0
docs/api/description/custom-rule.md

@@ -0,0 +1,629 @@
+# Custom rule
+
+After the 2.2.1 release, xmake not only natively supports the construction of multi-language files, but also allows users to implement complex unknown file builds by custom building rules.
+
+Custom build rules can have a set of file extensions associated to them using `set_extensions`.
+Once these extensions are associated to the rule a later call to `add_files` will automatically use this custom rule.
+Here is an example rule that will use Pandoc to convert markdown files added to a build target in to HTML files:
+
+```lua
+-- Define a build rule for a markdown file
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        import("core.project.depend")
+        import("utils.progress") -- it only for v2.5.9, we need use print to show progress below v2.5.8
+
+        -- 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 the file if its changed since last run
+        depend.on_changed(function ()
+            -- call pandoc to make a standalone html file from a markdown file
+            os.vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
+            progress.show(opt.progress, "${color.build.object}markdown %s", sourcefile)
+        end, {files = sourcefile})
+    end)
+
+target("test")
+    set_kind("object")
+
+    -- make the test target support the construction rules of the markdown file
+    add_rules("markdown")
+
+    -- adding a markdown file to build
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+::: tip NOTE
+Note that in xmake a rule is responsible for checking when targets are out of date and informing the user of ongoing progress.
+:::
+
+There is also an alternative to `on_build_file` in the form of `on_build_files` which allows you to process the entire set of files in one function call.
+
+A second form called `on_buildcmd_file` and `on_buildcmd_files` is instead declarative; rather than running arbitrary Lua to build a target it runs Lua to learn how those targets are built.
+The advantage to `buildcmd` is that those rules can be exported to makefiles which do not require xmake at all in order to run.
+
+We can use buildcmd to simplify it further, like this:
+
+```lua
+-- Define a build rule for a markdown file
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
+
+        -- make sure build directory exists
+        batchcmds:mkdir(target:targetdir())
+
+        -- replace .md with .html
+        local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
+
+        -- call pandoc to make a standalone html file from a markdown file
+        batchcmds:vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
+        batchcmds:show_progress(opt.progress, "${color.build.object}markdown %s", sourcefile)
+
+        -- only rebuild the file if its changed since last run
+        batchcmds:add_depfiles(sourcefile)
+    end)
+
+target("test")
+    set_kind("object")
+
+    -- make the test target support the construction rules of the markdown file
+    add_rules("markdown")
+
+    -- adding a markdown file to build
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+Files can be assigned to a specific rule regardless of their file extension. You do this by setting the `rule` custom property when adding the file like in the following example:
+
+```lua
+target("test")
+    add_files("src/test/*.md.in", {rule = "markdown"})
+```
+
+A target can be superimposed to apply multiple rules to more customize its own build behavior, and even support different build environments.
+
+::: tip NOTE
+Rules specified by `add_files("*.md", {rule = "markdown"})`, with a higher priority than the rule set by `add_rules("markdown")`.
+:::
+
+## rule
+
+- Defining rules
+
+```lua
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        os.cp(sourcefile, path.join(target:targetdir(), path.basename(sourcefile) .. ".html"))
+    end)
+```
+
+## add_deps
+
+- Adding rule dependencies
+
+Associated dependencies can bind a batch of rules, i.e. instead of adding rules one by one to a target using `add_rules()`, just apply a rule that will take effect for it and all its dependencies.
+
+For example
+
+```lua
+rule("foo")
+    add_deps("bar")
+
+rule("bar")
+   ...
+```
+
+We only need `add_rules("foo")` to apply both foo and bar rules.
+
+However, by default there is no order of execution between dependencies, and scripts such as `on_build_file` for foo and bar are executed in parallel, in an undefined order.
+
+To strictly control the order of execution, you can configure `add_deps("bar", {order = true})` to tell xmake that we need to execute scripts at the same level according to the order of dependencies.
+
+Example.
+
+```lua
+rule("foo")
+    add_deps("bar", {order = true})
+    on_build_file(function (target, sourcefile)
+    end)
+
+rule("bar")
+    on_build_file(function (target, sourcefile)
+    end)
+```
+
+bar's `on_build_file` will be executed first.
+
+::: tip NOTE
+To control the order of dependencies, we need xmake 2.7.2 or above to support this.
+:::
+
+However, this way of controlling dependencies only works if both foo and bar rules are custom rules, and this does not work if you want to insert your own rules to be executed before xmake's built-in rules.
+
+In this case, we need to use a more flexible dynamic rule creation and injection approach to modify the built-in rules.
+
+For example, if we want to execute the `on_build_file` script for a custom cppfront rule before the built-in `c++.build` rule, we can do this in the following way.
+
+```lua
+rule("cppfront")
+    set_extensions(".cpp2")
+    on_load(function (target)
+        local rule = target:rule("c++.build"):clone()
+        rule:add("deps", "cppfront", {order = true})
+        target:rule_add(rule)
+    end)
+    on_build_file(function (target, sourcefile, opt)
+        print("build cppfront file")
+    end)
+
+target("test")
+    set_kind("binary")
+    add_rules("cppfront")
+    add_files("src/*.cpp")
+    add_files("src/*.cpp2")
+```
+
+## add_imports
+
+- Add imported modules for all custom scripts
+
+For usage and description, please see: [target:add_imports](/api/description/project-target#add-imports), the usage is the same.
+
+## set_extensions
+
+- Setting the file extension type supported by the rule
+
+Apply rules to files with these suffixes by setting the supported extension file types, for example:
+
+```lua
+-- Define a build rule for a markdown file
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        os.cp(sourcefile, path.join(target:targetdir(), path.basename(sourcefile) .. ".html"))
+    end)
+
+target("test")
+    set_kind("binary")
+
+    -- Make the test target support the construction rules of the markdown file
+    add_rules("markdown")
+
+    -- Adding a markdown file to build
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+## on_load
+
+- Custom load script
+
+The load script used to implement the custom rules will be executed when the target is loaded. You can customize some target configurations in it, for example:
+
+```lua
+rule("test")
+    on_load(function (target)
+        target:add("defines", "TEST")
+    end)
+```
+
+## on_config
+
+- custom configuration script
+
+After `xmake config` is executed, this script is executed before Build, which is usually used for configuration work before compilation. It differs from on_load in that on_load is executed as soon as the target is loaded, and the execution timing is earlier.
+
+If some configuration cannot be configured prematurely in on_load, it can be configured in on_config.
+
+In addition, its execution time is earlier than before_build, and the approximate execution flow is as follows:
+
+```
+on_load -> after_load -> on_config -> before_build -> on_build -> after_build
+```
+
+## on_link
+
+- Custom link script
+
+The link script used to implement the custom rules overrides the default link behavior of the applied target, for example:
+
+```lua
+rule("test")
+    on_link(function (target)
+    end)
+```
+
+## on_build
+
+- Custom compilation script
+
+The build script used to implement the custom rules overrides the default build behavior of the target being applied, for example:
+
+```lua
+rule("markdown")
+    on_build(function (target)
+    end)
+```
+
+## on_clean
+
+- Custom cleanup script
+
+The cleanup script used to implement the custom rules will override the default cleanup behavior of the applied target, for example:
+
+```lua
+rule("markdown")
+    on_clean(function (target)
+        -- remove sourcefile.html
+    end)
+```
+
+## on_package
+
+- Custom packaging script
+
+A packaging script for implementing custom rules that overrides the default packaging behavior of the target being applied, for example:
+
+```lua
+rule("markdown")
+    on_package(function (target)
+        -- package sourcefile.html
+    end)
+```
+
+## on_install
+
+- Custom installation script
+
+An installation script for implementing custom rules that overrides the default installation behavior of the target being applied, for example:
+
+```lua
+rule("markdown")
+    on_install(function (target)
+    end)
+```
+
+## on_uninstall
+
+- Custom Uninstall Script
+
+An uninstall script for implementing custom rules that overrides the default uninstall behavior of the target being applied, for example:
+
+```lua
+rule("markdown")
+    on_uninstall(function (target)
+    end)
+```
+
+## on_build_file
+
+- Customizing the build script to process one source file at a time
+
+```lua
+rule("markdown")
+    on_build_file(function (target, sourcefile, opt)
+        print("%%%d: %s", opt.progress, sourcefile)
+    end)
+```
+
+The third parameter opt is an optional parameter, which is used to obtain some information state during the compilation process. For example, opt.progress is the compilation progress of the current period.
+
+## on_buildcmd_file
+
+- Custom batch compile script, process one source file at a time
+
+This is a new interface added in version 2.5.2. The script inside will not directly construct the source file, but will construct a batch command line task through the batchcmds object.
+When xmake actually executes the build, it executes these commands once.
+
+This is very useful for project generator plugins such as `xmake project`, because third-party project files generated by the generator do not support the execution of built-in scripts such as `on_build_files`.
+
+But the final result of `on_buildcmd_files` construction is a batch of original cmd command lines, which can be directly executed as custom commands for other project files.
+
+In addition, compared to `on_build_files`, it also simplifies the implementation of compiling extension files, is more readable and easy to configure, and is more user-friendly.
+
+```lua
+rule("foo")
+    set_extensions(".xxx")
+    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
+        batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
+        batchcmds:add_depfiles("/xxxxx/dependfile.h", ...)
+        -- batchcmds:add_depvalues(...)
+        -- batchcmds:set_depmtime(os.mtime(...))
+        -- batchcmds:set_depcache("xxxx.d")
+    end)
+```
+
+In addition to `batchcmds:vrunv`, we also support some other batch commands, such as:
+
+```lua
+batchcmds:show("hello %s", "xmake")
+batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile}, {envs = {LD_LIBRARY_PATH="/xxx"}})
+batchcmds:mkdir("/xxx") - and cp, mv, rm, ln ..
+batchcmds:compile(sourcefile_cx, objectfile, {configs = {includedirs = sourcefile_dir, languages = (sourcekind == "cxx" and "c++11")}})
+batchcmds:link(objectfiles, targetfile, {configs = {linkdirs = ""}})
+```
+
+At the same time, we also simplify the configuration of dependency execution in it. The following is a complete example:
+
+```lua
+rule("lex")
+    set_extensions(".l", ".ll")
+    on_buildcmd_file(function (target, batchcmds, sourcefile_lex, opt)
+
+        - imports
+        import("lib.detect.find_tool")
+
+        - get lex
+        local lex = assert(find_tool("flex") or find_tool("lex"), "lex not found!")
+
+        - get c/c++ source file for lex
+        local extension = path.extension(sourcefile_lex)
+        local sourcefile_cx = path.join(target:autogendir(), "rules", "lex_yacc", path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))
+
+        - add objectfile
+        local objectfile = target:objectfile(sourcefile_cx)
+        table.insert(target:objectfiles(), objectfile)
+
+        - add commands
+        batchcmds:show_progress(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)
+        batchcmds:mkdir(path.directory(sourcefile_cx))
+        batchcmds:vrunv(lex.program, {"-o", sourcefile_cx, sourcefile_lex})
+        batchcmds:compile(sourcefile_cx, objectfile)
+
+        - add deps
+        batchcmds:add_depfiles(sourcefile_lex)
+        batchcmds:set_depmtime(os.mtime(objectfile))
+        batchcmds:set_depcache(target:dependfile(objectfile))
+    end)
+```
+
+For a detailed description and background of this, see: [issue 1246](https://github.com/xmake-io/xmake/issues/1246)
+
+## on_build_files
+
+- Customizing the build script to process multiple source files at once
+
+Most of the custom build rules, each time processing a single file, output a target file, for example: a.c => a.o
+
+However, in some cases, we need to enter multiple source files together to build an object file, for example: a.c b.c d.c => x.o
+
+For this situation, we can achieve this by customizing this script:
+
+```lua
+rule("markdown")
+    on_build_files(function (target, sourcebatch, opt)
+        -- build some source files
+        for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+            -- ...
+        end
+    end)
+```
+
+## on_buildcmd_files
+
+- Customize batch compiling script, process multiple source files at once
+
+For a detailed description of this, see: [on_buildcmd_file](#on_buildcmd_file)
+
+```lua
+rule("foo")
+     set_extensions(".xxx")
+     on_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
+         for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+             batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
+         end
+     end)
+```
+
+## before_config
+
+- Custom pre-configuration script
+
+Used to implement the execution script before custom target configuration, for example:
+
+```lua
+rule("test")
+before_config(function (target)
+end)
+```
+
+It will be executed before on_config.
+
+## before_link
+
+- Custom pre-link script
+
+Execution scripts used to implement custom target links, for example:
+
+```lua
+rule("test")
+    before_link(function (target)
+    end)
+```
+
+## before_build
+
+- Custom pre-compilation script
+
+Used to implement the execution script before the custom target is built, for example:
+
+```lua
+rule("markdown")
+    before_build(function (target)
+    end)
+```
+
+## before_clean
+
+- Custom pre-cleanup script
+
+Used to implement the execution script before the custom target cleanup, for example:
+
+```lua
+rule("markdown")
+    before_clean(function (target)
+    end)
+```
+
+## before_package
+
+- Custom the pre-package script
+
+Used to implement the execution script before the custom target is packaged, for example:
+
+```lua
+rule("markdown")
+    before_package(function (target)
+    end)
+```
+
+## before_install
+
+- Custom pre-installation script
+
+Used to implement the execution script before the custom target installation, for example:
+
+```lua
+rule("markdown")
+    before_install(function (target)
+    end)
+```
+
+## before_uninstall
+
+- Custom pre-uninstall script
+
+Used to implement the execution script before the custom target is uninstalled, for example:
+
+```lua
+rule("markdown")
+    before_uninstall(function (target)
+    end)
+```
+
+## before_build_file
+
+- Custom pre-compilation script to process one source file at a time
+
+Similar to [on_build_file](#on_build_file), but the timing of this interface is called before compiling a source file.
+Generally used to preprocess some source files before compiling.
+
+## before_buildcmd_file
+
+- Customize the pre-compilation batch script, process one source file at a time
+
+Similar to the usage of [on_buildcmd_file](#on_buildcmd_file), but the time when this interface is called is before compiling a certain source file.
+It is generally used to pre-process certain source files before compilation.
+
+## before_build_files
+
+- Customize pre-compilation scripts to process multiple source files at once
+
+Similar to the usage of [on_build_files](#on_build_files), but the time when this interface is called is before compiling some source files,
+It is generally used to pre-process certain source files before compilation.
+
+## before_buildcmd_files
+
+- Customize the pre-compilation batch script to process multiple source files at once
+
+Similar to the usage of [on_buildcmd_files](#on_buildcmd_files), but the time when this interface is called is before compiling some source files,
+It is generally used to pre-process certain source files before compilation.
+
+## after_config
+
+- Custom post-configuration script
+
+Used to implement the execution script after custom target configuration, for example:
+
+```lua
+rule("test")
+after_config(function (target)
+end)
+```
+
+It will be executed after on_config.
+
+## after_link
+
+- Custom post-linking script
+
+The execution script used to implement the custom target link is similar to [after_link](#after_link).
+
+## after_build
+
+- Custom post-compilation script
+
+The execution script used to implement the custom target build is similar to [before_build](#before_build).
+
+## after_clean
+
+- Custom post-cleaning script
+
+The execution script used to implement the custom target cleanup is similar to [before_clean](#before_clean).
+
+## after_package
+
+- Custom post-packaging script
+
+The execution script used to implement the custom target package is similar to [before_package](#before_package).
+
+## after_install
+
+- Custom post-installation script
+
+The execution script used to implement the custom target installation is similar to [before_install](#before_install).
+
+## after_uninstall
+
+- Custom post-uninstallation Script
+
+The execution script used to implement the custom target uninstallation is similar to [before_uninstall](#before_uninstall).
+
+## after_build_file
+
+- Custom post-compilation scripts to process one source file at a time
+
+Similar to [on_build_file](#on_build_file), but the timing of this interface is called after compiling a source file.
+Generally used to post-process some compiled object files.
+
+## after_buildcmd_file
+
+- Customize the compiled batch script, process one source file at a time
+
+Similar to the usage of [on_buildcmd_file](#on_buildcmd_file), but the time when this interface is called is after compiling a certain source file,
+Generally used for post-processing some compiled object files.
+
+## after_build_files
+
+- Customize the compiled script to process multiple source files at once
+
+The usage is similar to [on_build_files](#on_build_files), but the time when this interface is called is after some source files are compiled,
+Generally used for post-processing some compiled object files.
+
+## after_buildcmd_files
+
+- Customize the compiled batch script to process multiple source files at once
+
+The usage is similar to [on_buildcmd_files](#on_buildcmd_files), but the time when this interface is called is after compiling some source files,
+Generally used for post-processing some compiled object files.
+
+## rule_end
+
+- End definition rules
+
+This is optional. If you want to manually end the rule definition, you can call it:
+
+```lua
+rule("test")
+    -- ..
+rule_end()
+```

+ 310 - 0
docs/api/description/custom-toolchain.md

@@ -0,0 +1,310 @@
+# Custom toolchain
+
+After version 2.3.4, xmake has supported custom toolchain in user's project xmake.lua, for example:
+
+```lua
+-- define toolchain
+toolchain("myclang")
+
+    -- mark as standalone toolchain
+    set_kind("standalone")
+
+    -- set toolset
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+    set_toolset("ld", "clang++", "clang")
+    set_toolset("sh", "clang++", "clang")
+    set_toolset("ar", "ar")
+    set_toolset("ex", "ar")
+    set_toolset("strip", "strip")
+    set_toolset("mm", "clang")
+    set_toolset("mxx", "clang", "clang++")
+    set_toolset("as", "clang")
+
+    add_defines("MYCLANG")
+
+    -- check toolchain
+    on_check(function (toolchain)
+        return import("lib.detect.find_tool")("clang")
+    end)
+
+    -- on load
+    on_load(function (toolchain)
+
+        -- get march
+        local march = is_arch("x86_64", "x64") and "-m64" or "-m32"
+
+        -- init flags for c/c++
+        toolchain:add("cxflags", march)
+        toolchain:add("ldflags", march)
+        toolchain:add("shflags", march)
+        if not is_plat("windows") and os.isdir("/usr") then
+            for _, includedir in ipairs({"/usr/local/include", "/usr/include"}) do
+                if os.isdir(includedir) then
+                    toolchain:add("includedirs", includedir)
+                end
+            end
+            for _, linkdir in ipairs({"/usr/local/lib", "/usr/lib"}) do
+                if os.isdir(linkdir) then
+                    toolchain:add("linkdirs", linkdir)
+                end
+            end
+        end
+
+        -- init flags for objc/c++  (with ldflags and shflags)
+        toolchain:add("mxflags", march)
+
+        -- init flags for asm
+        toolchain:add("asflags", march)
+    end)
+```
+
+Then use the following command to cut to the toolchain you defined:
+
+```bash
+$ xmake f --toolchain=myclang
+```
+
+Of course, we can also switch to the specified target directly to the custom toolchain through the `set_toolchains` interface.
+
+Before customizing the tool, we can run the following command to view the complete list of built-in toolchains to ensure that xmake does not provide it. If so, just use it directly. There is no need to define it yourself:
+
+```bash
+$ xmake show -l toolchains
+```
+
+## toolchain
+
+- Define toolchain
+
+It can be defined in the user project xmake.lua, or it can be independently defined by a separate xmake.lua to specifically define various toolchains
+
+```lua
+toolchain("myclang")
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+toolchain_end()
+```
+
+- Define cross toolchain
+
+We can also customize the configuration for different cross toolchain sdk in xmake.lua. Usually only need to specify sdkdir, xmake can automatically detect other configurations, such as cross and other information, for example:
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+toolchain_end()
+
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+```
+
+This is the most streamlined cross-toolchain configuration. It only sets the corresponding SDK path, and then marks it as a complete and independent toolchain by `set_kind("standalone")`.
+
+At this time, we can use the command line `--toolchain=my_toolchain` to manually switch to this toolchain.
+
+```console
+xmake f --toolchain=my_toolchain
+xmake
+```
+
+In addition, we can also directly bind it to the corresponding target through `set_toolchains` in xmake.lua, then only when this target is compiled, will we switch to our custom toolchain.
+
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+toolchain_end()
+
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+    set_toolchains("my_toolchain")
+```
+
+In this way, we no longer need to switch the toolchain manually, just execute xmake, and it will automatically switch to the my_toolchain toolchain by default.
+
+This is especially useful for embedded development, because there are many cross-compilation tool chains for embedded platforms, and we often need various switches to complete the compilation of different platforms.
+
+Therefore, we can place all toolchain definitions in a separate lua file to define, for example:
+
+```
+projectdir
+    -xmake.lua
+    -toolchains
+      -my_toolchain1.lua
+      -my_toolchain2.lua
+      -...
+```
+
+Then, we only need to introduce them through includes in xmake.lua, and bind different tool chains according to different custom platforms:
+
+```lua
+includes("toolchains/*.lua")
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+    if is_plat("myplat1") then
+        set_toolchains("my_toolchain1")
+    elseif is_plat("myplat2") then
+        set_toolchains("my_toolchain2")
+    end
+```
+
+In this way, we can quickly switch the designated platform directly when compiling to automatically switch the corresponding tool chain.
+
+```console
+xmake f -p myplat1
+xmake
+```
+
+If some cross-compilation toolchains are complex in structure and automatic detection is not enough, you can use `set_toolset`, `set_cross` and `set_bindir` interfaces according to the actual situation to configure other settings in a targeted manner.
+
+For example, in the following example, we also added some cxflags/ldflags and the built-in system library links.
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+    on_load(function (toolchain)
+        - add flags for arch
+        if toolchain:is_arch("arm") then
+            toolchain:add("cxflags", "-march=armv7-a", "-msoft-float", {force = true})
+            toolchain:add("ldflags", "-march=armv7-a", "-msoft-float", {force = true})
+        end
+        toolchain:add("ldflags", "--static", {force = true})
+        toolchain:add("syslinks", "gcc", "c")
+    end)
+```
+
+For more examples of custom toolchains, we can see the following interface documents, or refer to the built-in toolchain definition in the directory of xmake source code: [Internal Toolchain List](https://github.com/xmake-io /xmake/blob/master/xmake/toolchains/)
+
+## set_kind
+
+- Set toolchain type
+
+Currently only supports the setting of `standalone` type, which means that the current toolchain is an independent and complete toolchain, including a complete set of tool set configurations such as cc/cxx/ld/sh/ar and other compilers, archivers, and linkers.
+
+Usually used when a target is set with multiple toolchains at the same time, but only one independent toolchain can be effective at the same time. This configuration can ensure that the toolchains in effect are mutually exclusive. For example, the gcc/clang toolchain will not be simultaneously. Take effect.
+
+However, local toolchains such as yasm/nasm belong to the extension of additional local toolchains, and there is no need to set up standalone because two toolchains of clang/yasm may exist at the same time.
+
+::: tip NOTE
+Just remember that the toolchain with a complete compilation environment is set to standalone
+:::
+
+## set_toolset
+
+- Set Tool Set
+
+Used to set the name and path of each individual tool, for example:
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+    set_toolset("ld", "clang++", "clang")
+    set_toolset("sh", "clang++", "clang")
+    set_toolset("ar", "ar")
+    set_toolset("ex", "ar")
+    set_toolset("strip", "strip")
+    set_toolset("mm", "clang")
+    set_toolset("mxx", "clang", "clang++")
+    set_toolset("as", "clang")
+```
+
+If you provide multiple tool options, they will be searched in order.
+For example, the following will attempt to find `clang` and will then try to use `gcc` if `clang` cannot be found:
+
+```lua
+    set_toolset("cc", "clang", "gcc")
+```
+
+If your tool has the same name as a supported tool but a different name, you can specify this as `{generic tool name}@{tool name}`.
+For example the following will look for a C compiler called `clang-mytarget` suffix but will then assume that the tool behaves like clang:
+
+```lua
+    set_toolset("cc", "clang@clang-mytarget")
+```
+
+For details about this interface, you can see: [target.set_toolset](/api/description/project-target.html#set-toolset)
+
+## set_sdkdir
+
+- Set toolchain sdk directory path
+
+Usually we can configure the sdk directory through `xmake f --toolchain=myclang --sdk=xxx`, but each time the configuration is more cumbersome, we can also pre-configure to xmake.lua through this interface to facilitate quick switching.
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_sdkdir("/tmp/sdkdir")
+    set_toolset("cc", "clang")
+```
+
+## set_bindir
+
+- Set toolchain bin directory path
+
+Normally, we can configure the SDK directory through `xmake f --toolchain=myclang --bin=xxx`, but each time the configuration is more cumbersome, we can also pre-configure to xmake.lua through this interface, which is convenient for quick switching.
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_bindir("/tmp/sdkdir/bin")
+    set_toolset("cc", "clang")
+```
+
+## on_check
+
+- Detection toolchain
+
+It is used to detect whether the sdk or program where the specified toolchain exists exists on the current system. It is usually used in the case of multiple standalone toolchains to automatically detect and select an effective toolchain.
+
+For scenes specified manually by `xmake f --toolchain=myclang`, this detection configuration is not necessary and can be omitted.
+
+```lua
+toolchain("myclang")
+    on_check(function (toolchain)
+        return import("lib.detect.find_tool")("clang")
+    end)
+```
+
+## on_load
+
+- Load toolchain
+
+For some complex scenarios, we can dynamically and flexibly set various toolchain configurations in on_load, which is more flexible and powerful than setting in the description field:
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    on_load(function (toolchain)
+
+        -- set toolset
+        toolchain:set("toolset", "cc", "clang")
+        toolchain:set("toolset", "ld", "clang++")
+
+        -- init flags
+        local march = toolchain:is_arch("x86_64", "x64") and "-m64" or "-m32"
+        toolchain:add("cxflags", march)
+        toolchain:add("ldflags", march)
+        toolchain:add("shflags", march)
+    end)
+```
+
+## toolchain_end
+
+- End definition toolchain
+
+This is optional, if you want to manually end the definition of toolchain, you can call it:
+
+```lua
+toolchain("myclang")
+    - ..
+toolchain_end()
+```

+ 2 - 0
docs/api/index.md

@@ -13,6 +13,8 @@ outline: deep
 - [Project Target](/api/description/project-target)
 - [Configuration Option](/api/description/configuration-option)
 - [Plugin and Task](/api/description/plugin-and-task)
+- [Custom Rule](/api/description/custom-rule)
+- [Custom Toolchain](/api/description/custom-toolchain)
 - [Builtin Variables](/api/description/builtin-variables)
 
 

+ 2 - 0
docs/config.ts

@@ -106,6 +106,8 @@ function descriptionApiSidebar(): DefaultTheme.SidebarItem[] {
     { text: 'Project Targets', link: '/project-target' },
     { text: 'Configuration Option', link: '/configuration-option' },
     { text: 'Plugin and Task', link: '/plugin-and-task' },
+    { text: 'Custom Rule', link: '/custom-rule' },
+    { text: 'Custom Toolchain', link: '/custom-toolchain' },
     { text: 'Builtin Variables', link: '/builtin-variables' },
     {
       text: 'Next Steps',

+ 633 - 0
docs/zh/api/description/custom-rule.md

@@ -0,0 +1,633 @@
+# 自定义规则 {#custom-rule}
+
+2.2.1发布后,Xmake 不仅原生支持多语言文件的构建,还允许用户通过自定义构建规则实现复杂的未知文件构建。
+
+自定义构建规则可以使用 `set_extensions` 将一组文件扩展名关联到它们。
+
+一旦这些扩展与规则相关联,稍后对 `add_files` 的调用将自动使用此自定义规则。
+
+这是一个示例规则,它将使用 Pandoc 将添加到构建目标的 Markdown 文件转换为 HTML 文件:
+
+```lua
+-- Define a build rule for a markdown file
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        import("core.project.depend")
+        import("utils.progress") -- it only for v2.5.9, we need use print to show progress below v2.5.8
+
+        -- 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 the file if its changed since last run
+        depend.on_changed(function ()
+            -- call pandoc to make a standalone html file from a markdown file
+            os.vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
+            progress.show(opt.progress, "${color.build.object}markdown %s", sourcefile)
+        end, {files = sourcefile})
+    end)
+
+target("test")
+    set_kind("object")
+
+    -- make the test target support the construction rules of the markdown file
+    add_rules("markdown")
+
+    -- adding a markdown file to build
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+还有一种以 `on_build_files` 形式代替 `on_build_file` 的方法,它允许您在一个函数调用中处理整个文件集。
+
+第二种称为 `on_buildcmd_file` 和 `on_buildcmd_files` 的形式是声明性的;它不是运行任意 Lua 来构建目标,而是运行 Lua 来了解这些目标是如何构建的。
+
+`buildcmd` 的优点是可以将这些规则导出到根本不需要 xmake 即可运行的 makefile。
+
+我们可以使用 buildcmd 进一步简化它,如下所示:
+
+
+```lua
+-- Define a build rule for a markdown file
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
+
+        -- make sure build directory exists
+        batchcmds:mkdir(target:targetdir())
+
+        -- replace .md with .html
+        local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".html")
+
+        -- call pandoc to make a standalone html file from a markdown file
+        batchcmds:vrunv('pandoc', {"-s", "-f", "markdown", "-t", "html", "-o", targetfile, sourcefile})
+        batchcmds:show_progress(opt.progress, "${color.build.object}markdown %s", sourcefile)
+
+        -- only rebuild the file if its changed since last run
+        batchcmds:add_depfiles(sourcefile)
+    end)
+
+target("test")
+    set_kind("object")
+
+    -- make the test target support the construction rules of the markdown file
+    add_rules("markdown")
+
+    -- adding a markdown file to build
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+无论文件扩展名如何,文件都可以分配给特定规则。您可以通过在添加文件时设置 `rule` 自定义属性来完成此操作,如下例所示:
+
+```lua
+target("test")
+    -- ...
+    add_files("src/test/*.md.in", {rule = "markdown"})
+```
+
+一个target可以叠加应用多个rules去更加定制化实现自己的构建行为,甚至支持不同的构建环境。
+
+::: tip 注意
+通过`add_files("*.md", {rule = "markdown"})`方式指定的规则,优先级高于`add_rules("markdown")`设置的规则。
+:::
+
+## rule
+
+- 定义规则
+
+```lua
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        os.cp(sourcefile, path.join(target:targetdir(), path.basename(sourcefile) .. ".html"))
+    end)
+```
+
+## add_deps
+
+- 添加规则依赖
+
+关联依赖可以绑定一批规则,也就是不必对 target 挨个去使用 `add_rules()` 添加规则,只需要应用一个规则,就能生效它和它的所有依赖规则。
+
+例如:
+
+```lua
+rule("foo")
+    add_deps("bar")
+
+rule("bar")
+   ...
+```
+
+我们只需要 `add_rules("foo")`,就能同时应用 foo 和 bar 两个规则。
+
+但是,默认情况下,依赖之间是不存在执行的先后顺序的,foo 和 bar 的 `on_build_file` 等脚本是并行执行的,顺序未定义。
+
+如果要严格控制执行顺序,可以配置 `add_deps("bar", {order = true})`,告诉 xmake,我们需要根据依赖顺序来执行同级别的脚本。
+
+例如:
+
+```lua
+rule("foo")
+    add_deps("bar", {order = true})
+    on_build_file(function (target, sourcefile)
+    end)
+
+rule("bar")
+    on_build_file(function (target, sourcefile)
+    end)
+```
+
+bar 的 `on_build_file` 将会被先执行。
+
+::: tip 注意
+控制依赖顺序,我们需要 xmake 2.7.2 以上版本才支持。
+:::
+
+不过,这种控制依赖的方式,只适合 foo 和 bar 两个规则都是自定义规则,如果想要将自己的规则插入到 xmake 的内置规则之前执行,这就不适用了。
+
+这个时候,我们需要使用更加灵活的动态规则创建和注入的方式,去修改内置规则。
+
+例如,我们想在内置的 `c++.build` 规则之前,执行自定义 cppfront 规则的 `on_build_file` 脚本,我们可以通过下面的方式来实现。
+
+```lua
+rule("cppfront")
+    set_extensions(".cpp2")
+    on_load(function (target)
+        local rule = target:rule("c++.build"):clone()
+        rule:add("deps", "cppfront", {order = true})
+        target:rule_add(rule)
+    end)
+    on_build_file(function (target, sourcefile, opt)
+        print("build cppfront file")
+    end)
+
+target("test")
+    set_kind("binary")
+    add_rules("cppfront")
+    add_files("src/*.cpp")
+    add_files("src/*.cpp2")
+```
+
+## add_imports
+
+- 为所有自定义脚本预先导入扩展模块
+
+使用方式和说明请见:[target:add_imports](/zh/api/description/project-target#add-imports),用法相同。
+
+## set_extensions
+
+- 设置规则支持的文件扩展类型
+
+通过设置支持的扩展文件类型,将规则应用于带这些后缀的文件上,例如:
+
+```lua
+-- 定义一个markdown文件的构建规则
+rule("markdown")
+    set_extensions(".md", ".markdown")
+    on_build_file(function (target, sourcefile, opt)
+        os.cp(sourcefile, path.join(target:targetdir(), path.basename(sourcefile) .. ".html"))
+    end)
+
+target("test")
+    set_kind("binary")
+
+    -- 使test目标支持markdown文件的构建规则
+    add_rules("markdown")
+
+    -- 添加markdown文件的构建
+    add_files("src/*.md")
+    add_files("src/*.markdown")
+```
+
+## on_load
+
+- 自定义加载脚本
+
+用于实现自定规则的加载脚本,当加载target的时候,会被执行,可在里面自定义设置一些target配置,例如:
+
+```lua
+rule("test")
+    on_load(function (target)
+        target:add("defines", "TEST")
+    end)
+```
+
+## on_link
+
+- 自定义链接脚本
+
+用于实现自定规则的链接脚本,会覆盖被应用的target的默认链接行为,例如:
+
+```lua
+rule("test")
+    on_link(function (target)
+    end)
+```
+
+## on_config
+
+- 自定义配置脚本
+
+在 `xmake config` 执行完成后,Build 之前会执行此脚本,通常用于编译前的配置工作。它与 on_load 不同的是,on_load 只要 target 被加载就会执行,执行时机更早。
+
+如果一些配置,无法在 on_load 中过早配置,那么都可以在 on_config 中去配置它。
+
+另外,它的执行时机比 before_build 还要早,大概的执行流程如下:
+
+```
+on_load -> after_load -> on_config -> before_build -> on_build -> after_build
+```
+
+## on_build
+
+- 自定义编译脚本
+
+用于实现自定规则的构建脚本,会覆盖被应用的target的默认构建行为,例如:
+
+```lua
+rule("markdown")
+    on_build(function (target)
+    end)
+```
+
+## on_clean
+
+- 自定义清理脚本
+
+用于实现自定规则的清理脚本会,覆盖被应用的target的默认清理行为,例如:
+
+```lua
+rule("markdown")
+    on_clean(function (target)
+        -- remove sourcefile.html
+    end)
+```
+
+## on_package
+
+- 自定义打包脚本
+
+用于实现自定规则的打包脚本,覆盖被应用的target的默认打包行为, 例如:
+
+```lua
+rule("markdown")
+    on_package(function (target)
+        -- package sourcefile.html
+    end)
+```
+
+## on_install
+
+- 自定义安装脚本
+
+用于实现自定规则的安装脚本,覆盖被应用的target的默认安装行为, 例如:
+
+```lua
+rule("markdown")
+    on_install(function (target)
+    end)
+```
+
+## on_uninstall
+
+- 自定义卸载脚本
+
+用于实现自定规则的卸载脚本,覆盖被应用的target的默认卸载行为, 例如:
+
+```lua
+rule("markdown")
+    on_uninstall(function (target)
+    end)
+```
+
+## on_build_file
+
+- 自定义编译脚本,一次处理一个源文件
+
+```lua
+rule("markdown")
+    on_build_file(function (target, sourcefile, opt)
+        print("%%%d: %s", opt.progress, sourcefile)
+    end)
+```
+
+其中第三个参数opt是可选参数,用于获取一些编译过程中的信息状态,例如:opt.progress 为当期的编译进度。
+
+## on_buildcmd_file
+
+- 自定义批处理编译脚本,一次处理一个源文件
+
+这是 2.5.2 版本新加的接口,里面的脚本不会直接构建源文件,但是会通过 batchcmds 对象,构造一个批处理命令行任务,
+xmake 在实际执行构建的时候,一次性执行这些命令。
+
+这对于 `xmake project` 此类工程生成器插件非常有用,因为生成器生成的第三方工程文件并不支持 `on_build_files` 此类内置脚本的执行支持。
+
+但是 `on_buildcmd_files` 构造的最终结果,就是一批原始的 cmd 命令行,可以直接给其他工程文件作为 custom commands 来执行。
+
+另外,相比 `on_build_files`,它也简化对扩展文件的编译实现,更加的可读易配置,对用户也更加友好。
+
+```lua
+rule("foo")
+    set_extensions(".xxx")
+    on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
+        batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
+        batchcmds:add_depfiles("/xxxxx/dependfile.h",  ...)
+        -- batchcmds:add_depvalues(...)
+        -- batchcmds:set_depmtime(os.mtime(...))
+        -- batchcmds:set_depcache("xxxx.d")
+    end)
+```
+
+除了 `batchcmds:vrunv`,我们还支持一些其他的批处理命令,例如:
+
+```lua
+batchcmds:show("hello %s", "xmake")
+batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile}, {envs = {LD_LIBRARY_PATH="/xxx"}})
+batchcmds:mkdir("/xxx") -- and cp, mv, rm, ln ..
+batchcmds:compile(sourcefile_cx, objectfile, {configs = {includedirs = sourcefile_dir, languages = (sourcekind == "cxx" and "c++11")}})
+batchcmds:link(objectfiles, targetfile, {configs = {linkdirs = ""}})
+```
+
+同时,我们在里面也简化对依赖执行的配置,下面是一个完整例子:
+
+```lua
+rule("lex")
+    set_extensions(".l", ".ll")
+    on_buildcmd_file(function (target, batchcmds, sourcefile_lex, opt)
+
+        -- imports
+        import("lib.detect.find_tool")
+
+        -- get lex
+        local lex = assert(find_tool("flex") or find_tool("lex"), "lex not found!")
+
+        -- get c/c++ source file for lex
+        local extension = path.extension(sourcefile_lex)
+        local sourcefile_cx = path.join(target:autogendir(), "rules", "lex_yacc", path.basename(sourcefile_lex) .. (extension == ".ll" and ".cpp" or ".c"))
+
+        -- add objectfile
+        local objectfile = target:objectfile(sourcefile_cx)
+        table.insert(target:objectfiles(), objectfile)
+
+        -- add commands
+        batchcmds:show_progress(opt.progress, "${color.build.object}compiling.lex %s", sourcefile_lex)
+        batchcmds:mkdir(path.directory(sourcefile_cx))
+        batchcmds:vrunv(lex.program, {"-o", sourcefile_cx, sourcefile_lex})
+        batchcmds:compile(sourcefile_cx, objectfile)
+
+        -- add deps
+        batchcmds:add_depfiles(sourcefile_lex)
+        local dependfile = target:dependfile(objectfile)
+        batchcmds:set_depmtime(os.mtime(dependfile))
+        batchcmds:set_depcache(dependfile)
+    end)
+```
+
+`add_depfiles` 设置这个目标文件依赖的源文件。`set_depmtime` 设置目标文件的修改时间,如果有任意源文件的修改时间大于它,则认为需要重新生成这个目标文件。这里使用 dependfile 而不是 objectfile 的原因见 [issues 748](https://github.com/xmake-io/xmake/issues/748)。`set_depcache` 设置存储依赖信息的文件。
+
+关于这个的详细说明和背景,见:[issue 1246](https://github.com/xmake-io/xmake/issues/1246)
+
+## on_build_files
+
+- 自定义编译脚本,一次处理多个源文件
+
+大部分的自定义构建规则,每次都是处理单独一个文件,输出一个目标文件,例如:a.c => a.o
+
+但是,有些情况下,我们需要同时输入多个源文件一起构建生成一个目标文件,例如:a.c b.c d.c => x.o
+
+对于这种情况,我们可以通过自定义这个脚本来实现:
+
+```lua
+rule("markdown")
+    on_build_files(function (target, sourcebatch, opt)
+        -- build some source files
+        for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+            -- ...
+        end
+    end)
+```
+
+## on_buildcmd_files
+
+- 自定义批处理编译脚本,一次处理多个源文件
+
+关于这个的详细说明,见:[on_buildcmd_file](#on_buildcmd_file)
+
+```lua
+rule("foo")
+    set_extensions(".xxx")
+    on_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
+        for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+            batchcmds:vrunv("gcc", {"-o", objectfile, "-c", sourcefile})
+        end
+    end)
+```
+
+## before_config
+
+- 自定义配置前脚本
+
+用于实现自定义 target 配置前的执行脚本,例如:
+
+```lua
+rule("test")
+    before_config(function (target)
+    end)
+```
+
+它会在 on_config 之前被执行。
+
+## before_link
+
+- 自定义链接前脚本
+
+用于实现自定义target链接前的执行脚本,例如:
+
+```lua
+rule("test")
+    before_link(function (target)
+    end)
+```
+
+## before_build
+
+- 自定义编译前脚本
+
+用于实现自定义target构建前的执行脚本,例如:
+
+```lua
+rule("markdown")
+    before_build(function (target)
+    end)
+```
+
+## before_clean
+
+- 自定义清理前脚本
+
+用于实现自定义target清理前的执行脚本,例如:
+
+```lua
+rule("markdown")
+    before_clean(function (target)
+    end)
+```
+
+## before_package
+
+- 自定义打包前脚本
+
+用于实现自定义target打包前的执行脚本, 例如:
+
+```lua
+rule("markdown")
+    before_package(function (target)
+    end)
+```
+
+## before_install
+
+- 自定义安装前脚本
+
+用于实现自定义target安装前的执行脚本,例如:
+
+```lua
+rule("markdown")
+    before_install(function (target)
+    end)
+```
+
+## before_uninstall
+
+- 自定义卸载前脚本
+
+用于实现自定义target卸载前的执行脚本,例如:
+
+```lua
+rule("markdown")
+    before_uninstall(function (target)
+    end)
+```
+
+## before_build_file
+
+- 自定义编译前脚本,一次处理一个源文件
+
+跟[on_build_file](#on_build_file)用法类似,不过这个接口被调用的时机是在编译某个源文件之前,
+一般用于对某些源文件进行编译前的预处理。
+
+## before_buildcmd_file
+
+- 自定义编译前批处理脚本,一次处理一个源文件
+
+跟[on_buildcmd_file](#on_buildcmd_file)用法类似,不过这个接口被调用的时机是在编译某个源文件之前,
+一般用于对某些源文件进行编译前的预处理。
+
+## before_build_files
+
+- 自定义编译前脚本,一次处理多个源文件
+
+跟[on_build_files](#on_build_files)用法类似,不过这个接口被调用的时机是在编译某些源文件之前,
+一般用于对某些源文件进行编译前的预处理。
+
+## before_buildcmd_files
+
+- 自定义编译前批处理脚本,一次处理多个源文件
+
+跟[on_buildcmd_files](#on_buildcmd_files)用法类似,不过这个接口被调用的时机是在编译某些源文件之前,
+一般用于对某些源文件进行编译前的预处理。
+
+## after_config
+
+- 自定义配置后脚本
+
+用于实现自定义 target 配置后的执行脚本,例如:
+
+```lua
+rule("test")
+    after_config(function (target)
+    end)
+```
+
+它会在 on_config 之后被执行。
+
+## after_link
+
+- 自定义链接后脚本
+
+用于实现自定义target链接后的执行脚本,用法跟[before_link](#before_link)类似。
+
+## after_build
+
+- 自定义编译后脚本
+
+用于实现自定义target构建后的执行脚本,用法跟[before_build](#before_build)类似。
+
+## after_clean
+
+- 自定义清理后脚本
+
+用于实现自定义target清理后的执行脚本,用法跟[before_clean](#before_clean)类似。
+
+## after_package
+
+- 自定义打包后脚本
+
+用于实现自定义target打包后的执行脚本, 用法跟[before_package](#before_package)类似。
+
+## after_install
+
+- 自定义安装后脚本
+
+用于实现自定义target安装后的执行脚本,用法跟[before_install](#before_install)类似。
+
+## after_uninstall
+
+- 自定义卸载后脚本
+
+用于实现自定义target卸载后的执行脚本,用法跟[before_uninstall](#before_uninstall)类似。
+
+## after_build_file
+
+- 自定义编译后脚本,一次处理一个源文件
+
+跟[on_build_file](#on_build_file)用法类似,不过这个接口被调用的时机是在编译某个源文件之后,
+一般用于对某些编译后对象文件进行后期处理。
+
+## after_buildcmd_file
+
+- 自定义编译后批处理脚本,一次处理一个源文件
+
+跟[on_buildcmd_file](#on_buildcmd_file)用法类似,不过这个接口被调用的时机是在编译某个源文件之后,
+一般用于对某些编译后对象文件进行后期处理。
+
+## after_build_files
+
+- 自定义编译后脚本,一次处理多个源文件
+
+跟[on_build_files](#on_build_files)用法类似,不过这个接口被调用的时机是在编译某些源文件之后,
+一般用于对某些编译后对象文件进行后期处理。
+
+## after_buildcmd_files
+
+- 自定义编译后批处理脚本,一次处理多个源文件
+
+跟[on_buildcmd_files](#on_buildcmd_files)用法类似,不过这个接口被调用的时机是在编译某些源文件之后,
+一般用于对某些编译后对象文件进行后期处理。
+
+## rule_end
+
+- 结束定义规则
+
+这个是可选的,如果想要手动结束rule的定义,可以调用它:
+
+```lua
+rule("test")
+    -- ..
+rule_end()
+```

+ 296 - 0
docs/zh/api/description/custom-toolchain.md

@@ -0,0 +1,296 @@
+# 自定义工具链 {#custom-toolchain}
+
+在2.3.4版本之后,xmake已经支持在用户的项目xmake.lua中自定义工具链,例如:
+
+```lua
+-- define toolchain
+toolchain("myclang")
+
+    -- mark as standalone toolchain
+    set_kind("standalone")
+
+    -- set toolset
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+    set_toolset("ld", "clang++", "clang")
+    set_toolset("sh", "clang++", "clang")
+    set_toolset("ar", "ar")
+    set_toolset("ex", "ar")
+    set_toolset("strip", "strip")
+    set_toolset("mm", "clang")
+    set_toolset("mxx", "clang", "clang++")
+    set_toolset("as", "clang")
+
+    add_defines("MYCLANG")
+
+    -- check toolchain
+    on_check(function (toolchain)
+        return import("lib.detect.find_tool")("clang")
+    end)
+
+    -- on load
+    on_load(function (toolchain)
+
+        -- get march
+        local march = is_arch("x86_64", "x64") and "-m64" or "-m32"
+
+        -- init flags for c/c++
+        toolchain:add("cxflags", march)
+        toolchain:add("ldflags", march)
+        toolchain:add("shflags", march)
+        if not is_plat("windows") and os.isdir("/usr") then
+            for _, includedir in ipairs({"/usr/local/include", "/usr/include"}) do
+                if os.isdir(includedir) then
+                    toolchain:add("includedirs", includedir)
+                end
+            end
+            for _, linkdir in ipairs({"/usr/local/lib", "/usr/lib"}) do
+                if os.isdir(linkdir) then
+                    toolchain:add("linkdirs", linkdir)
+                end
+            end
+        end
+
+        -- init flags for objc/c++  (with ldflags and shflags)
+        toolchain:add("mxflags", march)
+
+        -- init flags for asm
+        toolchain:add("asflags", march)
+    end)
+```
+
+然后通过下面的命令切到自己定义的工具链就行了:
+
+```bash
+$ xmake f --toolchain=myclang
+```
+
+当然,我们也可以通过`set_toolchains`接口直接对指定target切换设置到自定义工具链。
+
+在自定义工具前,我们可以通过先运行以下命令,查看完整的内置工具链列表,确保xmake没有提供,如果有的话,直接使用就行了,没必要自己定义:
+
+```bash
+$ xmake show -l toolchains
+```
+
+## toolchain
+
+- 定义工具链
+
+可以在用户项目xmake.lua中定义,也可以通过includes独立到单独的xmake.lua去专门定义各种工具链
+
+```lua
+toolchain("myclang")
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+toolchain_end()
+```
+
+- 定义交叉工具链
+
+我们也可以在 xmake.lua 中针对不同的交叉工具链sdk进行自定义配置,通常只需要指定 sdkdir,xmake就可以自动检测其他的配置,比如 cross 等信息,例如:
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+toolchain_end()
+
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+```
+
+这是一个最精简的交叉工具链配置,仅仅设置了对应的sdk路径,然后通过 `set_kind("standalone")` 将其标记为完整独立的工具链。
+
+这个时候,我们就可以通过命令行 `--toolchain=my_toolchain` 去手动切换到此工具链来使用。
+
+```console
+xmake f --toolchain=my_toolchain
+xmake
+```
+
+另外,我们还可以直接在 xmake.lua 中通过 `set_toolchains` 将其绑定到对应的 target 上去,那么仅仅只在编译此 target 时候,才会切换到我们自定义的工具链。
+
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+toolchain_end()
+
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+    set_toolchains("my_toolchain")
+```
+
+这样,我们不再需要手动切换工具链了,只需要执行 xmake,就会默认自动切换到 my_toolchain 工具链。
+
+这对于嵌入式开发来讲尤其有用,因为嵌入式平台的交叉编译工具链非常多,我们经常需要各种切换来完成不同平台的编译。
+
+因此,我们可以将所有的工具链定义放置到独立的 lua 文件中去定义,例如:
+
+```
+projectdir
+    - xmake.lua
+    - toolchains
+      - my_toolchain1.lua
+      - my_toolchain2.lua
+      - ...
+```
+
+然后,我们只需要再 xmake.lua 中通过 includes 去引入它们,并根据不同的自定义平台,绑定不同的工具链:
+
+```lua
+includes("toolchains/*.lua")
+target("hello")
+    set_kind("binary")
+    add_files("apps/hello/*.c")
+    if is_plat("myplat1") then
+        set_toolchains("my_toolchain1")
+    elseif is_plat("myplat2") then
+        set_toolchains("my_toolchain2")
+    end
+```
+
+这样,我们就可以编译的时候,直接快速切换指定平台,来自动切换对应的工具链了。
+
+```console
+xmake f -p myplat1
+xmake
+```
+
+如果,有些交叉编译工具链结构复杂,自动检测还不足够,那么可以根据实际情况,使用 `set_toolset`, `set_cross` 和 `set_bindir` 等接口,针对性的配置上其他的设置。
+
+例如下面的例子,我们还额外添加了一些 cxflags/ldflags 以及内置的系统库 links。
+
+```lua
+toolchain("my_toolchain")
+    set_kind("standalone")
+    set_sdkdir("/tmp/arm-linux-musleabi-cross")
+    on_load(function (toolchain)
+        -- add flags for arch
+        if toolchain:is_arch("arm") then
+            toolchain:add("cxflags", "-march=armv7-a", "-msoft-float", {force = true})
+            toolchain:add("ldflags", "-march=armv7-a", "-msoft-float", {force = true})
+        end
+        toolchain:add("ldflags", "--static", {force = true})
+        toolchain:add("syslinks", "gcc", "c")
+    end)
+```
+
+更多自定义工具链的例子,我们可以看下面的接口文档,也可以到 xmake 的源码的目录参考内置的工具链定义:[内部工具链列表](https://github.com/xmake-io/xmake/blob/master/xmake/toolchains/)
+
+## set_kind
+
+- 设置工具链类型
+
+目前仅支持设置为`standalone`类型,表示当前工具链是独立完整的工具链,包括cc/cxx/ld/sh/ar等编译器、归档器、链接器等一整套工具集的配置。
+
+通常用于某个target被同时设置了多个工具链的情况,但同时只能生效一个独立工具链,通过此配置可以保证生效的工具链存在互斥关系,比如gcc/clang工具链不会同时生效。
+
+而像yasm/nasm这种局部工具链,属于附加的局部工具链扩展,不用设置standalone,因为clang/yasm两个工具链有可能同时存在。
+
+::: tip 注意
+只要记住,存在完整编译环境的工具链,都设置为standalone就行了
+:::
+
+## set_toolset
+
+- 设置工具集
+
+用于设置每个单独工具名和路径,例如:
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_toolset("cc", "clang")
+    set_toolset("cxx", "clang", "clang++")
+    set_toolset("ld", "clang++", "clang")
+    set_toolset("sh", "clang++", "clang")
+    set_toolset("ar", "ar")
+    set_toolset("ex", "ar")
+    set_toolset("strip", "strip")
+    set_toolset("mm", "clang")
+    set_toolset("mxx", "clang", "clang++")
+    set_toolset("as", "clang")
+```
+
+关于这个接口的详情,可以看下:[target.set_toolset](/zh/api/description/project-target.html#set-toolset)
+
+## set_sdkdir
+
+- 设置工具链sdk目录路径
+
+通常我们可以通过`xmake f --toolchain=myclang --sdk=xxx` 来配置 sdk 目录,但是每次配置比较繁琐,我们也可以通过此接口预先配置到 xmake.lua 中去,方便快速切换使用。
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_sdkdir("/tmp/sdkdir")
+    set_toolset("cc", "clang")
+```
+
+## set_bindir
+
+- 设置工具链bin目录路径
+
+通常我们可以通过`xmake f --toolchain=myclang --bin=xxx`来配置 sdk 目录,但是每次配置比较繁琐,我们也可以通过此接口预先配置到 xmake.lua 中去,方便快速切换使用。
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    set_bindir("/tmp/sdkdir/bin")
+    set_toolset("cc", "clang")
+```
+
+## on_check
+
+- 检测工具链
+
+用于检测指定工具链所在sdk或者程序在当前系统上是否存在,通常用于多个 standalone 工具链的情况,进行自动探测和选择有效工具链。
+
+而对于 `xmake f --toolchain=myclang` 手动指定的场景,此检测配置不是必须的,可以省略。
+
+```lua
+toolchain("myclang")
+    on_check(function (toolchain)
+        return import("lib.detect.find_tool")("clang")
+    end)
+```
+
+## on_load
+
+- 加载工具链
+
+对于一些复杂的场景,我们可以在 on_load 中动态灵活的设置各种工具链配置,比在描述域设置更加灵活,更加强大:
+
+```lua
+toolchain("myclang")
+    set_kind("standalone")
+    on_load(function (toolchain)
+
+        -- set toolset
+        toolchain:set("toolset", "cc", "clang")
+        toolchain:set("toolset", "ld", "clang++")
+
+        -- init flags
+        local march = toolchain:is_arch("x86_64", "x64") and "-m64" or "-m32"
+        toolchain:add("cxflags", march)
+        toolchain:add("ldflags", march)
+        toolchain:add("shflags", march)
+    end)
+```
+
+## toolchain_end
+
+- 结束定义工具链
+
+这个是可选的,如果想要手动结束 toolchain 的定义,可以调用它:
+
+```lua
+toolchain("myclang")
+    -- ..
+toolchain_end()
+```

+ 2 - 0
docs/zh/api/index.md

@@ -13,6 +13,8 @@ outline: deep
 - [工程目标](/zh/api/description/project-target)
 - [配置选项](/zh/api/description/configuration-option)
 - [插件任务](/zh/api/description/plugin-and-task)
+- [自定义规则](/zh/api/description/custom-rule)
+- [自定义工具链](/zh/api/description/custom-toolchain)
 - [内建变量](/zh/api/description/builtin-variables)
 
 ## 脚本域 API {#scripts-api}

+ 2 - 0
docs/zh/config.ts

@@ -187,6 +187,8 @@ function descriptionApiSidebar(): DefaultTheme.SidebarItem[] {
     { text: '工程目标', link: '/project-target' },
     { text: '配置选项', link: '/configuration-option' },
     { text: '插件任务', link: '/plugin-and-task' },
+    { text: '自定义规则', link: '/custom-rule' },
+    { text: '自定义工具链', link: '/custom-toolchain' },
     { text: '内建变量', link: '/builtin-variables' },
     {
       text: '下一步',