├── .github └── workflows │ ├── ci.yml │ └── cross.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── build.lua ├── build_info.lua ├── install.lua ├── lib.rs └── modules │ ├── builder.lua │ ├── include_scanner │ ├── clang_gcc.lua │ └── msvc.lua │ └── utils.lua └── tests ├── .gitignore ├── check-mode ├── Cargo.toml ├── build.rs ├── src │ ├── foo.c │ └── main.rs └── xmake.lua ├── cxx ├── Cargo.toml ├── build.rs ├── src │ ├── foo.cpp │ └── main.rs └── xmake.lua ├── namespace ├── Cargo.toml ├── build.rs ├── src │ ├── foo.c │ └── main.rs └── xmake.lua ├── packages ├── README.md ├── sht-shf-shb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── main.rs │ │ └── target.c │ └── xmake.lua ├── stt-shf-shb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── main.rs │ │ └── target.c │ └── xmake.lua ├── stt-shf-stb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── main.rs │ │ └── target.c │ └── xmake.lua ├── stt-stf-shb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── main.rs │ │ └── target.c │ └── xmake.lua └── stt-stf-stb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ ├── main.rs │ └── target.c │ └── xmake.lua ├── phony ├── Cargo.toml ├── build.rs ├── src │ └── main.rs └── xmake.lua ├── shared ├── Cargo.toml ├── build.rs ├── src │ ├── foo.c │ └── main.rs └── xmake.lua └── static ├── Cargo.toml ├── build.rs ├── src ├── foo.c └── main.rs └── xmake.lua /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests Suite 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ main ] 7 | push: 8 | branches: [ main ] 9 | 10 | jobs: 11 | test: 12 | name: Test Suite 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | arch: [x86, x64] 19 | include: 20 | - os: ubuntu-latest 21 | arch: x86 22 | rust_target: i686-unknown-linux-gnu 23 | - os: ubuntu-latest 24 | arch: x64 25 | rust_target: x86_64-unknown-linux-gnu 26 | - os: windows-latest 27 | arch: x86 28 | rust_target: i686-pc-windows-msvc 29 | - os: windows-latest 30 | arch: x64 31 | rust_target: x86_64-pc-windows-msvc 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | 36 | - name: Setup xmake 37 | uses: xmake-io/github-action-setup-xmake@v1 38 | with: 39 | xmake-version: branch@dev 40 | 41 | - name: Install Rust target 42 | run: rustup target add ${{ matrix.rust_target }} 43 | 44 | - name: Install Linux x86 Dependencies 45 | if: matrix.os == 'ubuntu-latest' && matrix.arch == 'x86' 46 | run: | 47 | sudo dpkg --add-architecture i386 48 | sudo apt update 49 | sudo apt install -y gcc-multilib g++-multilib 50 | 51 | - name: Run xmake package tests 52 | run: | 53 | cargo test --package xmake 54 | cargo test --doc 55 | 56 | - name: Find and run test binaries 57 | shell: bash 58 | run: | 59 | find tests -type f -name "Cargo.toml" | while read -r cargo_toml; do 60 | crate_dir=$(dirname "$cargo_toml") 61 | crate_name=$(basename "$crate_dir") 62 | cargo run -p "$crate_name" --target ${{ matrix.rust_target }} 63 | done -------------------------------------------------------------------------------- /.github/workflows/cross.yml: -------------------------------------------------------------------------------- 1 | name: Cross Compilation 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ main ] 7 | push: 8 | branches: [ main ] 9 | 10 | jobs: 11 | cross-compile: 12 | name: Cross Compilation x86 on x64 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | include: 19 | - os: ubuntu-latest 20 | target: i686-unknown-linux-gnu 21 | - os: windows-latest 22 | target: i686-pc-windows-msvc 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - name: Setup xmake 28 | uses: xmake-io/github-action-setup-xmake@v1 29 | with: 30 | xmake-version: branch@dev 31 | 32 | - name: Install Rust target 33 | run: rustup target add ${{ matrix.target }} 34 | 35 | - name: Install Linux Dependencies 36 | if: matrix.os == 'ubuntu-latest' 37 | run: | 38 | sudo dpkg --add-architecture i386 39 | sudo apt-get update 40 | sudo apt-get install -y gcc-multilib 41 | 42 | - name: Cross-compile static test 43 | run: cargo build -p static --target ${{ matrix.target }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | 4 | 5 | .xmake/ 6 | build/ 7 | 8 | .vscode/ 9 | *.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xmake" 3 | version = "0.3.2" 4 | edition = "2021" 5 | license = "MIT" 6 | keywords = ["build-dependencies"] 7 | repository = "https://github.com/A2va/xmake-rs" 8 | homepage = "https://github.com/A2va/xmake-rs" 9 | documentation = "https://docs.rs/xmake" 10 | description = """ 11 | A build dependency for running `xmake` to build a native library 12 | """ 13 | categories = ["development-tools::build-utils"] 14 | 15 | [dependencies] 16 | cc = "1.0.72" 17 | 18 | [workspace] 19 | members = [ 20 | ".", 21 | "tests/cxx", 22 | "tests/check-mode", 23 | "tests/namespace", 24 | "tests/shared", 25 | "tests/static", 26 | "tests/phony", 27 | "tests/packages/sht-shf-shb", 28 | "tests/packages/stt-shf-shb", 29 | "tests/packages/stt-stf-stb", 30 | "tests/packages/stt-stf-shb", 31 | "tests/packages/stt-shf-stb" 32 | ] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 A2va 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xmake 2 | 3 | A build dependency for running the [xmake](https://xmake.io/) build tool to compile a native 4 | library. 5 | 6 | ```toml 7 | # Cargo.toml 8 | [build-dependencies] 9 | xmake = "0.3.2" 10 | ``` 11 | 12 | The XMake executable is assumed to be `xmake` unless the `XMAKE` 13 | environmental variable is set. 14 | There is some examples in the `tests` folder of the repo. 15 | 16 | ## Difference from cmake-rs 17 | 18 | Broadly speaking, xmake-rs is very similar to cmake-rs, but there are two advantages: 19 | * Xmake is known to be simpler than cmake, with a built-in package manager so using it improve the development workflow. 20 | * Xmake-rs supports automatic linking, so it is no longer necessary to use `rustc-link-lib` and `rustc-link-search` as in cmake-rs. 21 | 22 | ## Cross-Compilation Support 23 | 24 | If you need to cross-compile your project, xmake provides a built-in package manager that can set up the emscripten or Android NDK toolchains. The first two lines of the code snippet below enter a single package environment, overwriting the previous environment. However, the last line enters both the emscripten and NDK environments simultaneously. 25 | ``` 26 | xrepo env -b ndk shell 27 | xrepo env -b emscripten shell 28 | xrepo env -b "emscripten, ndk" shell 29 | ``` 30 | After executing one of these commands, xmake will automatically detect either the emscripten or NDK toolchain. 31 | 32 | If you prefer to use your own toolchain, you can set either the ANDROID_NDK_HOME or EMSCRIPTEN_HOME environment variables to specify the path to the corresponding toolchain. -------------------------------------------------------------------------------- /src/build.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2024 A2va 2 | 3 | -- Permission is hereby granted, free of charge, to any 4 | -- person obtaining a copy of this software and associated 5 | -- documentation files (the "Software"), to deal in the 6 | -- Software without restriction, including without 7 | -- limitation the rights to use, copy, modify, merge, 8 | -- publish, distribute, sublicense, and/or sell copies of 9 | -- the Software, and to permit persons to whom the Software 10 | -- is furnished to do so, subject to the following 11 | -- conditions: 12 | 13 | -- The above copyright notice and this permission notice 14 | -- shall be included in all copies or substantial portions 15 | -- of the Software. 16 | 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | -- DEALINGS IN THE SOFTWARE. 26 | 27 | import("core.project.config") 28 | import("core.project.project") 29 | import("core.base.hashset") 30 | import("core.base.task") 31 | 32 | import("modules.utils") 33 | 34 | function main() 35 | local oldir = os.cd(os.projectdir()) 36 | 37 | task.run("config") 38 | 39 | local _, targetsname = utils.get_targets() 40 | targetsname = hashset.from(targetsname) 41 | 42 | for name, target in pairs(project.targets()) do 43 | target:set("default", targetsname:has(utils.get_namespace_target(target))) 44 | end 45 | 46 | task.run("build") 47 | os.cd(oldir) 48 | end -------------------------------------------------------------------------------- /src/build_info.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2024 A2va 2 | 3 | -- Permission is hereby granted, free of charge, to any 4 | -- person obtaining a copy of this software and associated 5 | -- documentation files (the "Software"), to deal in the 6 | -- Software without restriction, including without 7 | -- limitation the rights to use, copy, modify, merge, 8 | -- publish, distribute, sublicense, and/or sell copies of 9 | -- the Software, and to permit persons to whom the Software 10 | -- is furnished to do so, subject to the following 11 | -- conditions: 12 | 13 | -- The above copyright notice and this permission notice 14 | -- shall be included in all copies or substantial portions 15 | -- of the Software. 16 | 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | -- DEALINGS IN THE SOFTWARE. 26 | 27 | import("core.cache.memcache") 28 | import("core.cache.localcache") 29 | 30 | import("core.project.config") 31 | import("core.project.depend") 32 | import("core.project.project") 33 | 34 | import("core.base.task") 35 | import("core.base.hashset") 36 | 37 | import("async.runjobs") 38 | import("lib.detect.find_library") 39 | 40 | import("modules.utils") 41 | import("modules.builder") 42 | 43 | 44 | -- return true if the cache needs to be invalidated 45 | function _cache_invalidation() 46 | local changed = false 47 | 48 | local config_hash = hash.sha256(config.filepath()) 49 | changed = config_hash ~= localcache.get("xmake-rs", "config_hash") 50 | if changed then 51 | localcache.set("xmake-rs", "config_hash", config_hash) 52 | localcache.save("xmake-rs") 53 | return changed 54 | end 55 | 56 | local mtimes = project.mtimes() 57 | local mtimes_prev = localcache.get("config", "mtimes") 58 | if mtimes_prev then 59 | for file, mtime in pairs(mtimes) do 60 | -- modified? reconfig and rebuild it 61 | local mtime_prev = mtimes_prev[file] 62 | if not mtime_prev or mtime > mtime_prev then 63 | changed = true 64 | break 65 | end 66 | end 67 | end 68 | 69 | return changed 70 | end 71 | 72 | function _include_scanner(target) 73 | local memcache = memcache.cache("include_scanner") 74 | local cachekey = tostring(target) 75 | local scanner = memcache:get2("include_scanner", cachekey) 76 | if scanner == nil then 77 | if target:has_tool("cxx", "clang", "clangxx") or target:has_tool("cxx", "gcc", "gxx") then 78 | scanner = import("modules.include_scanner.clang_gcc", {anonymous = true}) 79 | elseif target:has_tool("cxx", "cl") then 80 | scanner = import("modules.include_scanner.msvc", {anonymous = true}) 81 | else 82 | local _, toolname = target:tool("cxx") 83 | raise("compiler(%s): is not supported!", toolname) 84 | end 85 | memcache:set2("include_scanner", cachekey, scanner) 86 | end 87 | return scanner 88 | end 89 | 90 | function _print_infos(infos) 91 | 92 | print("__xmakers_start__") 93 | for k, v in table.orderpairs(infos) do 94 | -- links are handled differently 95 | if k == "links" then 96 | v = table.imap(v, function(index, v) 97 | return format("%s/%s", v.name, v.kind) 98 | end) 99 | end 100 | 101 | if type(v) == "table" then 102 | v = table.concat(v, "|") 103 | end 104 | 105 | print(k .. ":" .. tostring(v)) 106 | end 107 | print("__xmakers_stop__") 108 | end 109 | 110 | function _get_linkdirs(target, opt) 111 | local opt = opt or {} 112 | 113 | local linkdirs = table.imap(utils.get_from_target(target, "linkdirs", "*"), function(index, linkdir) 114 | if not path.is_absolute(linkdir) then 115 | linkdir = path.absolute(linkdir, os.projectdir()) 116 | end 117 | return linkdir 118 | end) 119 | linkdirs = hashset.from(linkdirs) 120 | local envs = target:pkgenvs() 121 | 122 | local values = "" 123 | if envs then 124 | values = is_plat("windows") and envs.PATH or envs.LD_LIBRARY_PATH 125 | if is_plat("macosx") then 126 | values = envs.DYLD_LIBRARY_PATH 127 | end 128 | end 129 | 130 | for _, v in ipairs(path.splitenv(values)) do 131 | if not linkdirs:has(v) then 132 | linkdirs:insert(v) 133 | end 134 | end 135 | 136 | if is_plat("windows") and opt.runenvs then 137 | for _, toolchain in ipairs(target:toolchains()) do 138 | local runenvs = toolchain:runenvs() 139 | if runenvs and runenvs.PATH then 140 | for _, env in ipairs(path.splitenv(runenvs.PATH)) do 141 | linkdirs:insert(env) 142 | end 143 | end 144 | end 145 | end 146 | 147 | return linkdirs:to_array() 148 | end 149 | 150 | function _find_kind(link, linkdirs) 151 | -- in find_library of the xmake codebase, shared comes before static in the kind table. 152 | -- but on windows there is a problem because .lib (static librairies) files can appear in a shared package/target 153 | -- thus guessing wrongly the link kind, to prevent that we must detect shared library before static one. 154 | local lib = find_library(link, linkdirs, {kind = {"shared", "static"}, plat = config.plat()}) 155 | if not lib then 156 | return "unknown" 157 | end 158 | return lib.kind 159 | end 160 | 161 | function _get_links(target) 162 | local linkdirs = _get_linkdirs(target) 163 | 164 | local syslinks = hashset.from(utils.get_from_target(target, "syslinks", "*")) 165 | local frameworks = hashset.from(utils.get_from_target(target, "frameworks", "*")) 166 | 167 | local have_groups_order = (target:get_from("linkgroups", "*") ~= nil) or (target:get_from("linkorders", "*") ~= nil) 168 | 169 | -- a target can have a different file name from the target name (set_basename) 170 | -- so map the target name to the generated file name 171 | local filename_map = {} 172 | for _, target in pairs(project.targets()) do 173 | if target:is_static() or target:is_shared() then 174 | filename_map[target:linkname()] = utils.get_namespace_target(target) 175 | end 176 | end 177 | local is_target = function(link) 178 | return filename_map[link] ~= nil 179 | end 180 | 181 | -- collects all phony targets (which may have dependencies or packages) but have no links to them. 182 | local phony_target = {} 183 | for _, dep in ipairs(target:orderdeps()) do 184 | table.insert(phony_target, dep:kind() == "phony") 185 | end 186 | local is_phony_target = function(idx) 187 | return phony_target[idx] 188 | end 189 | 190 | local items = builder.orderlinks(target) 191 | local links = {} 192 | 193 | for idx, item in ipairs(items) do 194 | local values = (type(item.values[1]) == "table") and table.unpack(item.values) or item.values 195 | local is_syslinks = item.name == "syslinks" 196 | local is_frameworks = item.name == "frameworks" 197 | 198 | if not (have_groups_order or is_syslinks or is_frameworks) and not (is_target(values[1]) or is_phony_target(idx)) then 199 | goto continue 200 | end 201 | 202 | for _, value in ipairs(values) do 203 | local kind = is_syslinks and "syslinks" or nil 204 | kind = is_frameworks and "frameworks" or kind 205 | 206 | if item.name == "linkgroups" then 207 | if syslinks:has(value) then 208 | kind = "syslinks" 209 | elseif frameworks:has(value) then 210 | kind = "frameworks" 211 | end 212 | end 213 | 214 | -- if we link to a target, take it's kind 215 | if is_target(value) then 216 | kind = project.target(filename_map[value]):kind() 217 | end 218 | 219 | if not kind then 220 | kind = _find_kind(value, linkdirs) 221 | end 222 | table.insert(links, {name = value, kind = kind}) 223 | end 224 | ::continue:: 225 | end 226 | 227 | return links 228 | end 229 | 230 | function _link_info(binary_target, opt) 231 | opt = opt or {} 232 | 233 | local xmake_rs = localcache.cache("xmake-rs") 234 | local cache_key = binary_target:data("xmakers-cachekey") 235 | 236 | local in_cache = xmake_rs:get2("linkinfo", cache_key) ~= nil 237 | if (not opt.recheck) and in_cache then 238 | local linkinfo = xmake_rs:get2("linkinfo", cache_key) 239 | local linkdirs = linkinfo.linkdirs 240 | local links = linkinfo.links 241 | 242 | return {links = links, linkdirs = linkdirs} 243 | end 244 | 245 | local links = _get_links(binary_target) 246 | local linkdirs = _get_linkdirs(binary_target, {runenvs = true}) 247 | 248 | xmake_rs:set2("linkinfo", cache_key, {linkdirs = linkdirs, links = links}) 249 | xmake_rs:save() 250 | 251 | return {links = links, linkdirs = linkdirs} 252 | end 253 | 254 | function _stl_usage(target, sourcebatch, opt) 255 | opt = opt or {} 256 | local xmake_rs = localcache.cache("xmake-rs") 257 | local modules_cache = localcache.cache("cxxmodules") 258 | 259 | -- collect the files that use the stl previously 260 | local files = opt.recheck and xmake_rs:get2("stl", target:name()) or {} 261 | files = hashset.from(files) 262 | 263 | -- wrap the on_changed callback 264 | local stl_detection = function(index, sourcefile, callback) 265 | local dependfile = target:dependfile(target:objectfile(sourcefile)) 266 | local result = false 267 | 268 | depend.on_changed(function() 269 | result = callback(index) 270 | end, {dependfile = dependfile, files = {sourcefile}, changed = target:is_rebuilt(), dryrun = opt.recheck}) 271 | return result 272 | end 273 | 274 | local process_modules_files = function(index) 275 | local sourcefile = sourcebatch.sourcefiles[index] 276 | local objectfile = sourcebatch.objectfiles[index] 277 | 278 | local fileinfo = modules_cache:get3("modules", target:name() .. "/" .. "c++.build.modules.builder", objectfile) 279 | assert(fileinfo, "cxxmodules cache is empty. build the the target first") 280 | 281 | local requires = fileinfo.requires 282 | for require, v in pairs(requires) do 283 | -- import std; 284 | if require == "std" or require == "std.compat" then 285 | files:insert(sourcefile) 286 | return true -- signal to stop 287 | end 288 | 289 | -- import ; 290 | if utils.is_stl_used(target, require) then 291 | files:insert(sourcefile) 292 | return true -- signal to stop 293 | end 294 | end 295 | 296 | -- the file doesn't use the stl anymore 297 | if files:has(sourcefile) then 298 | files:remove(sourcefile) 299 | end 300 | return false 301 | end 302 | 303 | local process_files = function(index) 304 | local sourcefile = sourcebatch.sourcefiles[index] 305 | local includes = _include_scanner(target).scan(target, sourcefile, opt) 306 | 307 | if utils.is_stl_used(target, includes, {strict = true}) then 308 | files:insert(sourcefile) 309 | return true -- signal to stop 310 | end 311 | 312 | -- the file doesn't use the stl anymore 313 | if files:has(sourcefile) then 314 | files:remove(sourcefile) 315 | end 316 | return false 317 | end 318 | 319 | local process_files = opt.modules and process_modules_files or process_files 320 | 321 | if opt.batchjobs then 322 | -- if a project use the stl it will detected in the first few files 323 | -- so a large jobs number is not necessary 324 | local jobs = 2 325 | try { 326 | function () 327 | runjobs(target:name() .. "_stl_scanner", function(index) 328 | if stl_detection(index, sourcebatch.sourcefiles[index], process_files) then 329 | raise() -- little hack to stop the async jobs 330 | end 331 | end, {comax = jobs, total = #sourcebatch.sourcefiles}) 332 | end 333 | } 334 | else 335 | for index, sourcefile in ipairs(sourcebatch.sourcefiles) do 336 | if stl_detection(index, sourcebatch.sourcefiles[index], process_files) then 337 | break 338 | end 339 | end 340 | 341 | end 342 | 343 | xmake_rs:set2("stl", target:name(), files:to_array()) 344 | xmake_rs:save() 345 | return files:size() > 0 346 | end 347 | 348 | function _stl_info(targets, opt) 349 | opt = opt or {} 350 | local is_cxx_used = false 351 | local is_stl_used = false 352 | 353 | for _, target in pairs(targets) do 354 | local sourcebatches, _ = target:sourcebatches() 355 | local is_cxx = sourcebatches["c++.build"] ~= nil 356 | local is_cxx_modules = sourcebatches["c++.build.modules"] ~= nil 357 | 358 | is_cxx_used = is_cxx or is_cxx_modules 359 | if is_cxx then 360 | is_stl_used = _stl_usage(target, sourcebatches["c++.build"], {batchjobs = true, recheck = opt.recheck}) 361 | end 362 | 363 | if is_cxx_modules then 364 | is_stl_used = is_stl_used or _stl_usage(target, sourcebatches["c++.build.modules"], {modules = true, recheck = opt.recheck}) 365 | end 366 | 367 | if is_stl_used then 368 | break 369 | end 370 | end 371 | 372 | return {cxx_used = is_cxx_used, stl_used = is_stl_used} 373 | end 374 | 375 | function _includedirs_info(binary_target, opt) 376 | -- sysincludedirs is used when querying includedirs for packages on a target 377 | local results, sources = binary_target:get_from("sysincludedirs", "dep::*") 378 | 379 | local includedirs_by_source = {} 380 | 381 | for idx, includedirs in ipairs(results) do 382 | local source = sources[idx]:split("/")[2] 383 | local source, package = table.unpack(source:split("::")) 384 | 385 | if source == "package" then 386 | includedirs_by_source["includedirs_package.".. package] = includedirs 387 | end 388 | end 389 | 390 | local results, sources = binary_target:get_from("includedirs", "dep::*") 391 | 392 | for idx, includedirs in ipairs(results) do 393 | local source = sources[idx] 394 | local source, target = table.unpack(source:split("::")) 395 | 396 | if source == "dep" then 397 | includedirs_by_source["includedirs_target." .. target] = includedirs 398 | end 399 | end 400 | return includedirs_by_source 401 | end 402 | 403 | 404 | function main() 405 | -- load the config to get the correct options 406 | local oldir = os.cd(os.projectdir()) 407 | config.load() 408 | project.load_targets() 409 | -- task.run("config") 410 | 411 | local recheck = _cache_invalidation() 412 | local targets, _ = utils.get_targets() 413 | 414 | local binary_target = utils.create_binary_target(targets) 415 | 416 | local infos = _link_info(binary_target, {recheck = recheck}) 417 | table.join2(infos, _stl_info(targets, {recheck = recheck})) 418 | table.join2(infos, _includedirs_info(binary_target)) 419 | 420 | -- print(infos) 421 | _print_infos(infos) 422 | os.cd(oldir) 423 | end -------------------------------------------------------------------------------- /src/install.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2024 A2va 2 | 3 | -- Permission is hereby granted, free of charge, to any 4 | -- person obtaining a copy of this software and associated 5 | -- documentation files (the "Software"), to deal in the 6 | -- Software without restriction, including without 7 | -- limitation the rights to use, copy, modify, merge, 8 | -- publish, distribute, sublicense, and/or sell copies of 9 | -- the Software, and to permit persons to whom the Software 10 | -- is furnished to do so, subject to the following 11 | -- conditions: 12 | 13 | -- The above copyright notice and this permission notice 14 | -- shall be included in all copies or substantial portions 15 | -- of the Software. 16 | 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | -- DEALINGS IN THE SOFTWARE. 26 | 27 | import("core.cache.memcache") 28 | import("core.project.config") 29 | import("core.project.project") 30 | 31 | import("core.base.hashset") 32 | import("core.base.task") 33 | import("target.action.install") 34 | 35 | import("modules.utils") 36 | 37 | function main() 38 | local oldir = os.cd(os.projectdir()) 39 | 40 | config.load() 41 | project.load_targets() 42 | 43 | local targets, _ = utils.get_targets() 44 | local binary_target = utils.create_binary_target(targets) 45 | 46 | -- create a dummy executable file 47 | -- because this excutable is not existent xmake cannot check which dlls are dependants so disable stripping 48 | if not os.exists(path.directory(binary_target:targetfile())) then 49 | os.mkdir(path.directory(binary_target:targetfile())) 50 | end 51 | os.touch(binary_target:targetfile()) 52 | memcache.cache("core.project.project"):set2("policies", "install.strip_packagelibs", false) 53 | binary_target:set("installdir", os.getenv("XMAKERS_INSTALL_DIR")) 54 | install(binary_target) 55 | 56 | os.cd(oldir) 57 | end -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A build dependency for running `xmake` to build a native library 2 | //! 3 | //! This crate provides some necessary boilerplate and shim support for running 4 | //! the system `xmake` command to build a native library. 5 | //! 6 | //! The builder-style configuration allows for various variables and such to be 7 | //! passed down into the build as well. 8 | //! 9 | //! ## Installation 10 | //! 11 | //! Add this to your `Cargo.toml`: 12 | //! 13 | //! ```toml 14 | //! [build-dependencies] 15 | //! xmake = "0.3.2" 16 | //! ``` 17 | //! 18 | //! ## Examples 19 | //! 20 | //! ```no_run 21 | //! use xmake; 22 | //! 23 | //! // Builds the project in the directory located in `libfoo`, and link it 24 | //! xmake::build("libfoo"); 25 | //! ``` 26 | //! 27 | //! ```no_run 28 | //! use xmake::Config; 29 | //! 30 | //! Config::new("libfoo") 31 | //! .option("bar", "true") 32 | //! .env("XMAKE", "path/to/xmake") 33 | //! .build(); 34 | //! ``` 35 | #![deny(missing_docs)] 36 | 37 | use std::collections::{HashMap, HashSet}; 38 | use std::env; 39 | use std::io::{BufRead, BufReader, ErrorKind}; 40 | use std::path::{Path, PathBuf}; 41 | use std::process::{Command, Stdio}; 42 | use std::str::FromStr; 43 | 44 | // The version of xmake that is required for this crate to work. 45 | // https://github.com/xmake-io/xmake/releases/tag/v2.9.6 46 | const XMAKE_MINIMUM_VERSION: Version = Version::new(2, 9, 6); 47 | 48 | /// Represents the different kinds of linkage for a library. 49 | /// 50 | /// The `LinkKind` enum represents the different ways a library can be linked: 51 | #[derive(Debug, Clone, PartialEq, Eq)] 52 | pub enum LinkKind { 53 | /// The library is statically linked, meaning its code is included directly in the final binary. 54 | Static, 55 | /// The library is dynamically linked, meaning the final binary references the library at runtime. 56 | Dynamic, 57 | /// The library is a system library, meaning it is provided by the operating system and not included in the final binary. 58 | System, 59 | /// The library is a framework, like [`LinkKind::System`], it is provided by the operating system but used only on macos. 60 | Framework, 61 | /// The library is unknown, meaning its kind could not be determined. 62 | Unknown, 63 | } 64 | 65 | /// Represents the source when querying some information from [`BuildInfo`]. 66 | pub enum Source { 67 | /// Coming from an xmake target 68 | Target, 69 | /// Coming from an xmake package 70 | Package, 71 | /// Both of them 72 | Both, 73 | } 74 | 75 | /// Represents a single linked library. 76 | /// 77 | /// The `Link` struct contains information about a single linked library, including its name and the kind of linkage. 78 | #[derive(Debug, Clone, PartialEq, Eq)] 79 | pub struct Link { 80 | /// The name of the linked library. 81 | name: String, 82 | /// The kind of linkage for the library. 83 | kind: LinkKind, 84 | } 85 | 86 | /// Represents the link information for a build. 87 | /// 88 | /// The `BuildLinkInfo` struct contains information about the libraries that are linked in a build, including the directories they are located in and the individual `Link` structs. 89 | #[derive(Default)] 90 | pub struct BuildInfo { 91 | /// The directories that contain the linked libraries. 92 | linkdirs: Vec, 93 | /// The individual linked libraries. 94 | links: Vec, 95 | /// All the includirs coming from the packages 96 | includedirs_package: HashMap>, 97 | /// All the includirs coming from the targets 98 | includedirs_target: HashMap>, 99 | /// Whether the build uses the C++. 100 | use_cxx: bool, 101 | /// Whether the build uses the C++ standard library. 102 | use_stl: bool, 103 | } 104 | 105 | /// Represents errors that can occur when parsing a string to it's `BuildInfo` representation. 106 | #[derive(Debug, PartialEq, Eq)] 107 | pub enum ParsingError { 108 | /// Given kind did not match any of the `LinkKind` variants. 109 | InvalidKind, 110 | /// Missing at least one key to construct `BuildInfo`. 111 | MissingKey, 112 | /// Link string is malformed. 113 | MalformedLink, 114 | /// Multiple values when it's not supposed to 115 | MultipleValues, 116 | /// Error when converting string a type 117 | ParseError, 118 | } 119 | 120 | impl Link { 121 | /// Returns the name of the library as a string. 122 | pub fn name(&self) -> &str { 123 | &self.name 124 | } 125 | /// Returns the kind of linkage for the library. 126 | pub fn kind(&self) -> &LinkKind { 127 | &self.kind 128 | } 129 | 130 | /// Creates a new `Link` with the given name and kind. 131 | pub fn new(name: &str, kind: LinkKind) -> Link { 132 | Link { 133 | name: name.to_string(), 134 | kind: kind, 135 | } 136 | } 137 | } 138 | 139 | impl BuildInfo { 140 | /// Returns the directories that contain the linked libraries. 141 | pub fn linkdirs(&self) -> &[PathBuf] { 142 | &self.linkdirs 143 | } 144 | 145 | /// Returns the individual linked libraries. 146 | pub fn links(&self) -> &[Link] { 147 | &self.links 148 | } 149 | 150 | /// Returns whether the build uses C++. 151 | pub fn use_cxx(&self) -> bool { 152 | self.use_cxx 153 | } 154 | 155 | /// Returns whether the build uses C++ standard library. 156 | pub fn use_stl(&self) -> bool { 157 | self.use_stl 158 | } 159 | 160 | /// Retrieves the include directories for the specific target/package given it's name. 161 | /// If `*` is given as a name all the includedirs will be returned. 162 | pub fn includedirs>(&self, source: Source, name: S) -> Vec { 163 | let name = name.as_ref(); 164 | let mut result = Vec::new(); 165 | 166 | let sources = match source { 167 | Source::Target => vec![&self.includedirs_target], 168 | Source::Package => vec![&self.includedirs_package], 169 | Source::Both => vec![&self.includedirs_target, &self.includedirs_package], 170 | }; 171 | 172 | for map in sources { 173 | if name == "*" { 174 | result.extend(map.values().cloned().flatten()); 175 | } else if let Some(dirs) = map.get(name) { 176 | result.extend(dirs.clone()); 177 | } 178 | } 179 | 180 | result 181 | } 182 | } 183 | 184 | impl FromStr for LinkKind { 185 | type Err = ParsingError; 186 | fn from_str(s: &str) -> Result { 187 | match s { 188 | "static" => Ok(LinkKind::Static), 189 | "shared" => Ok(LinkKind::Dynamic), 190 | "system" => Ok(LinkKind::System), 191 | "framework" => Ok(LinkKind::Framework), 192 | "unknown" => Ok(LinkKind::Unknown), 193 | _ => Err(ParsingError::InvalidKind), 194 | } 195 | } 196 | } 197 | 198 | impl FromStr for Link { 199 | type Err = ParsingError; 200 | fn from_str(s: &str) -> Result { 201 | const NUMBER_OF_PARTS: usize = 2; 202 | 203 | let parts: Vec<_> = s.split("/").collect(); 204 | if parts.len() != NUMBER_OF_PARTS { 205 | return Err(ParsingError::MalformedLink); 206 | } 207 | 208 | let kind_result: LinkKind = parts[1].parse()?; 209 | Ok(Link { 210 | name: parts[0].to_string(), 211 | kind: kind_result, 212 | }) 213 | } 214 | } 215 | 216 | impl FromStr for BuildInfo { 217 | type Err = ParsingError; 218 | fn from_str(s: &str) -> Result { 219 | let map = parse_info_pairs(s); 220 | 221 | let directories: Vec = parse_field(&map, "linkdirs")?; 222 | let links: Vec = parse_field(&map, "links")?; 223 | 224 | let use_cxx: bool = parse_field(&map, "cxx_used")?; 225 | let use_stl: bool = parse_field(&map, "stl_used")?; 226 | 227 | let packages = subkeys_of(&map, "includedirs_package"); 228 | let mut includedirs_package = HashMap::new(); 229 | for package in packages { 230 | let dirs: Vec = parse_field(&map, format!("includedirs_package.{}", package))?; 231 | includedirs_package.insert(package.to_string(), dirs); 232 | } 233 | 234 | let targets = subkeys_of(&map, "includedirs_target"); 235 | let mut includedirs_target = HashMap::new(); 236 | for target in targets { 237 | let dirs: Vec = parse_field(&map, format!("includedirs_target.{}", target))?; 238 | includedirs_target.insert(target.to_string(), dirs); 239 | } 240 | 241 | Ok(BuildInfo { 242 | linkdirs: directories, 243 | links: links, 244 | use_cxx: use_cxx, 245 | use_stl: use_stl, 246 | includedirs_package: includedirs_package, 247 | includedirs_target: includedirs_target, 248 | }) 249 | } 250 | } 251 | 252 | #[derive(Default)] 253 | struct ConfigCache { 254 | build_info: BuildInfo, 255 | plat: Option, 256 | arch: Option, 257 | xmake_version: Option, 258 | env: HashMap>, 259 | } 260 | 261 | impl ConfigCache { 262 | /// Returns the platform string for this configuration. 263 | /// Panic if the config has not been done yet 264 | fn plat(&self) -> &String { 265 | return self.plat.as_ref().unwrap(); 266 | } 267 | 268 | /// Returns the architecture string for this configuration. 269 | /// Panic if the config has not been done yet 270 | fn arch(&self) -> &String { 271 | return self.arch.as_ref().unwrap(); 272 | } 273 | } 274 | 275 | /// Builder style configuration for a pending XMake build. 276 | pub struct Config { 277 | path: PathBuf, 278 | targets: Option, 279 | verbose: bool, 280 | auto_link: bool, 281 | out_dir: Option, 282 | mode: Option, 283 | options: Vec<(String, String)>, 284 | env: Vec<(String, String)>, 285 | static_crt: Option, 286 | runtimes: Option, 287 | no_stl_link: bool, 288 | cache: ConfigCache, 289 | } 290 | 291 | /// Builds the native library rooted at `path` with the default xmake options. 292 | /// This will return the directory in which the library was installed. 293 | /// 294 | /// # Examples 295 | /// 296 | /// ```no_run 297 | /// use xmake; 298 | /// 299 | /// // Builds the project in the directory located in `libfoo`, and link it 300 | /// xmake::build("libfoo"); 301 | /// ``` 302 | /// 303 | pub fn build>(path: P) { 304 | Config::new(path.as_ref()).build() 305 | } 306 | 307 | impl Config { 308 | /// Creates a new blank set of configuration to build the project specified 309 | /// at the path `path`. 310 | pub fn new>(path: P) -> Config { 311 | Config { 312 | path: env::current_dir().unwrap().join(path), 313 | targets: None, 314 | verbose: false, 315 | auto_link: true, 316 | out_dir: None, 317 | mode: None, 318 | options: Vec::new(), 319 | env: Vec::new(), 320 | static_crt: None, 321 | runtimes: None, 322 | no_stl_link: false, 323 | cache: ConfigCache::default(), 324 | } 325 | } 326 | 327 | /// Sets the xmake targets for this compilation. 328 | /// Note: This is different from rust target (os and arch). 329 | /// ``` 330 | /// use xmake::Config; 331 | /// let mut config = xmake::Config::new("libfoo"); 332 | /// config.targets("foo"); 333 | /// config.targets("foo,bar"); 334 | /// config.targets(["foo", "bar"]); // You can also pass a Vec or Vec<&str> 335 | /// ``` 336 | pub fn targets(&mut self, targets: T) -> &mut Config { 337 | self.targets = Some(targets.as_comma_separated()); 338 | self 339 | } 340 | 341 | /// Sets verbose output. 342 | pub fn verbose(&mut self, value: bool) -> &mut Config { 343 | self.verbose = value; 344 | self 345 | } 346 | 347 | /// Configures if targets and their dependencies should be linked. 348 | ///
Without configuring `no_stl_link`, the C++ standard library will be linked, if used in the project.
349 | /// This option defaults to `true`. 350 | pub fn auto_link(&mut self, value: bool) -> &mut Config { 351 | self.auto_link = value; 352 | self 353 | } 354 | 355 | /// Configures if the C++ standard library should be linked. 356 | /// 357 | /// This option defaults to `false`. 358 | /// If false and no runtimes options is set, the runtime flag passed to xmake configuration will be not set at all. 359 | pub fn no_stl_link(&mut self, value: bool) -> &mut Config { 360 | self.no_stl_link = value; 361 | self 362 | } 363 | 364 | /// Sets the output directory for this compilation. 365 | /// 366 | /// This is automatically scraped from `$OUT_DIR` which is set for Cargo 367 | /// build scripts so it's not necessary to call this from a build script. 368 | pub fn out_dir>(&mut self, out: P) -> &mut Config { 369 | self.out_dir = Some(out.as_ref().to_path_buf()); 370 | self 371 | } 372 | 373 | /// Sets the xmake mode for this compilation. 374 | pub fn mode(&mut self, mode: &str) -> &mut Config { 375 | self.mode = Some(mode.to_string()); 376 | self 377 | } 378 | 379 | /// Configure an option for the `xmake` processes spawned by 380 | /// this crate in the `build` step. 381 | pub fn option(&mut self, key: K, value: V) -> &mut Config 382 | where 383 | K: AsRef, 384 | V: AsRef, 385 | { 386 | self.options 387 | .push((key.as_ref().to_owned(), value.as_ref().to_owned())); 388 | self 389 | } 390 | 391 | /// Configure an environment variable for the `xmake` processes spawned by 392 | /// this crate in the `build` step. 393 | pub fn env(&mut self, key: K, value: V) -> &mut Config 394 | where 395 | K: AsRef, 396 | V: AsRef, 397 | { 398 | self.env 399 | .push((key.as_ref().to_owned(), value.as_ref().to_owned())); 400 | self 401 | } 402 | 403 | /// Configures runtime type (static or not) 404 | /// 405 | /// This option defaults to `false`. 406 | pub fn static_crt(&mut self, static_crt: bool) -> &mut Config { 407 | self.static_crt = Some(static_crt); 408 | self 409 | } 410 | 411 | /// Sets the runtimes to use for this compilation. 412 | /// 413 | /// This method takes a collection of runtime names, which will be passed to 414 | /// the `xmake` command during the build process. The runtimes specified here 415 | /// will be used to determine the appropriate C++ standard library to link 416 | /// against. 417 | /// Common values: 418 | /// - `MT` 419 | /// - `MTd` 420 | /// - `MD` 421 | /// - `MDd` 422 | /// - `c++_static` 423 | /// - `c++_shared` 424 | /// - `stdc++_static` 425 | /// - `stdc++_shared` 426 | /// - `gnustl_static` 427 | /// - `gnustl_shared` 428 | /// - `stlport_shared` 429 | /// - `stlport_static` 430 | /// ``` 431 | /// use xmake::Config; 432 | /// let mut config = xmake::Config::new("libfoo"); 433 | /// config.runtimes("MT,c++_static"); 434 | /// config.runtimes(["MT", "c++_static"]); // You can also pass a Vec or Vec<&str> 435 | /// ``` 436 | pub fn runtimes(&mut self, runtimes: T) -> &mut Config { 437 | self.runtimes = Some(runtimes.as_comma_separated()); 438 | self 439 | } 440 | 441 | /// Run this configuration, compiling the library with all the configured 442 | /// options. 443 | /// 444 | /// This will run both the configuration command as well as the 445 | /// command to build the library. 446 | pub fn build(&mut self) { 447 | self.config(); 448 | 449 | let mut cmd = self.xmake_command(); 450 | 451 | // In case of xmake is waiting to download something 452 | cmd.arg("--yes"); 453 | 454 | if let Some(targets) = &self.targets { 455 | // :: is used to handle namespaces in xmake but it interferes with the env separator 456 | // on linux, so we use a different separator 457 | cmd.env("XMAKERS_TARGETS", targets.replace("::", "||")); 458 | } 459 | 460 | cmd.run_script("build.lua"); 461 | 462 | if let Some(info) = self.get_build_info() { 463 | self.cache.build_info = info; 464 | } 465 | 466 | if self.auto_link { 467 | self.link(); 468 | } 469 | } 470 | 471 | /// Returns a reference to the `BuildInfo` associated with this build. 472 | ///
Note: Accessing this information before the build step will result in non-representative data.
473 | pub fn build_info(&self) -> &BuildInfo { 474 | &self.cache.build_info 475 | } 476 | 477 | // Run the configuration with all the configured 478 | /// options. 479 | fn config(&mut self) { 480 | self.check_version(); 481 | 482 | let mut cmd = self.xmake_command(); 483 | cmd.task("config"); 484 | 485 | // In case of xmake is waiting to download something 486 | cmd.arg("--yes"); 487 | 488 | let dst = self 489 | .out_dir 490 | .clone() 491 | .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR"))); 492 | 493 | cmd.arg(format!("--buildir={}", dst.display())); 494 | 495 | // Cross compilation 496 | let host = getenv_unwrap("HOST"); 497 | let target = getenv_unwrap("TARGET"); 498 | 499 | let os = getenv_unwrap("CARGO_CFG_TARGET_OS"); 500 | 501 | let plat = self.get_xmake_plat(); 502 | cmd.arg(format!("--plat={}", plat)); 503 | 504 | if host != target { 505 | let arch = self.get_xmake_arch(); 506 | cmd.arg(format!("--arch={}", arch)); 507 | 508 | if plat == "android" { 509 | if let Ok(ndk) = env::var("ANDROID_NDK_HOME") { 510 | cmd.arg(format!("--ndk={}", ndk)); 511 | } 512 | cmd.arg(format!("--toolchain={}", "ndk")); 513 | } 514 | 515 | if plat == "wasm" { 516 | if let Ok(emscripten) = env::var("EMSCRIPTEN_HOME") { 517 | cmd.arg(format!("--emsdk={}", emscripten)); 518 | } 519 | cmd.arg(format!("--toolchain={}", "emcc")); 520 | } 521 | 522 | if plat == "cross" { 523 | let mut c_cfg = cc::Build::new(); 524 | c_cfg 525 | .cargo_metadata(false) 526 | .opt_level(0) 527 | .debug(false) 528 | .warnings(false) 529 | .host(&host) 530 | .target(&target); 531 | 532 | // Attempt to find the cross compilation sdk 533 | // Let cc find it for us 534 | // Usually a compiler is inside bin folder and xmake expect the entire 535 | // sdk folder 536 | let compiler = c_cfg.get_compiler(); 537 | let sdk = compiler.path().ancestors().nth(2).unwrap(); 538 | 539 | cmd.arg(format!("--sdk={}", sdk.display())); 540 | cmd.arg(format!("--cross={}-{}", arch, os)); 541 | cmd.arg(format!("--toolchain={}", "cross")); 542 | } 543 | } 544 | 545 | // Configure the runtimes 546 | if let Some(runtimes) = &self.runtimes { 547 | cmd.arg(format!("--runtimes={}", runtimes)); 548 | } else if let Some(runtimes) = self.get_runtimes() { 549 | if !self.no_stl_link { 550 | cmd.arg(format!("--runtimes={}", runtimes)); 551 | } 552 | } 553 | 554 | // Compilation mode: release, debug... 555 | let mode = self.get_mode(); 556 | cmd.arg("-m").arg(mode); 557 | 558 | // Option 559 | for (key, val) in self.options.iter() { 560 | let option = format!("--{}={}", key.clone(), val.clone(),); 561 | cmd.arg(option); 562 | } 563 | 564 | cmd.run(); 565 | } 566 | 567 | fn link(&mut self) { 568 | let dst = self.install(); 569 | let plat = self.get_xmake_plat(); 570 | 571 | let build_info = &mut self.cache.build_info; 572 | 573 | for directory in build_info.linkdirs() { 574 | // Reference: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-search 575 | println!("cargo:rustc-link-search=all={}", directory.display()); 576 | } 577 | 578 | // Special link search path for dynamic libraries, because 579 | // the path are appended to the dynamic library search path environment variable 580 | // only if there are within OUT_DIR 581 | let linux_shared_libs_folder = dst.join("lib"); 582 | println!( 583 | "cargo:rustc-link-search=native={}", 584 | linux_shared_libs_folder.display() 585 | ); 586 | println!( 587 | "cargo:rustc-link-search=native={}", 588 | dst.join("bin").display() 589 | ); 590 | 591 | build_info.linkdirs.push(linux_shared_libs_folder.clone()); 592 | build_info.linkdirs.push(dst.join("bin")); 593 | 594 | let mut shared_libs = HashSet::new(); 595 | 596 | for link in build_info.links() { 597 | match link.kind() { 598 | LinkKind::Static => println!("cargo:rustc-link-lib=static={}", link.name()), 599 | LinkKind::Dynamic => { 600 | println!("cargo:rustc-link-lib=dylib={}", link.name()); 601 | shared_libs.insert(link.name()); 602 | } 603 | LinkKind::Framework if plat == "macosx" => { 604 | println!("cargo:rustc-link-lib=framework={}", link.name()) 605 | } 606 | // For rust, framework type is only for macosx but can be used on multiple system in xmake 607 | // so fallback to the system libraries case 608 | LinkKind::System | LinkKind::Framework => { 609 | println!("cargo:rustc-link-lib={}", link.name()) 610 | } 611 | // Let try cargo handle the rest 612 | LinkKind::Unknown => println!("cargo:rustc-link-lib={}", link.name()), 613 | } 614 | } 615 | 616 | // In some cases, xmake does not include all the shared libraries in the link cmd (for example, in the sht-shf-shb test), 617 | // leading to build failures on the rust side because it expected to link them, so the solution is to fetches all the libs from the install directory. 618 | // Since I cannot know the real order of the links, this can cause some problems on some projects. 619 | if plat == "linux" && linux_shared_libs_folder.exists() { 620 | let files = std::fs::read_dir(dst.join("lib")).unwrap(); 621 | for entry in files { 622 | if let Ok(file) = entry { 623 | let file_name = file.file_name(); 624 | let file_name = file_name.to_str().unwrap(); 625 | if file_name.ends_with(".so") || file_name.matches(r"\.so\.\d+").count() > 0 { 626 | if let Some(lib_name) = file_name.strip_prefix("lib") { 627 | let name = if let Some(dot_pos) = lib_name.find(".so") { 628 | &lib_name[..dot_pos] 629 | } else { 630 | lib_name 631 | }; 632 | 633 | if !shared_libs.contains(name) { 634 | println!("cargo:rustc-link-lib=dylib={}", name); 635 | } 636 | } 637 | } 638 | } 639 | } 640 | } 641 | 642 | if !self.no_stl_link && self.build_info().use_stl() { 643 | if let Some(runtimes) = &self.runtimes { 644 | let plat = self.cache.plat(); 645 | 646 | let stl: Option<&[&str]> = match plat.as_str() { 647 | "linux" => { 648 | Some(&["c++_static", "c++_shared", "stdc++_static", "stdc++_shared"]) 649 | } 650 | "android" => Some(&[ 651 | "c++_static", 652 | "c++_shared", 653 | "gnustl_static", 654 | "gnustl_shared", 655 | "stlport_static", 656 | "stlport_shared", 657 | ]), 658 | _ => None, 659 | }; 660 | 661 | if let Some(stl) = stl { 662 | // Try to match the selected runtime with the available runtimes 663 | for runtime in runtimes.split(",") { 664 | if stl.contains(&runtime) { 665 | let (name, _) = runtime.split_once("_").unwrap(); 666 | let kind = match runtime.contains("static") { 667 | true => "static", 668 | false => "dylib", 669 | }; 670 | println!(r"cargo:rustc-link-lib={}={}", kind, name); 671 | break; 672 | } 673 | } 674 | } 675 | } else { 676 | if let Some(runtime) = self.get_runtimes() { 677 | let (name, _) = runtime.split_once("_").unwrap(); 678 | let kind = match runtime.contains("static") { 679 | true => "static", 680 | false => "dylib", 681 | }; 682 | println!(r"cargo:rustc-link-lib={}={}", kind, name); 683 | } 684 | } 685 | } 686 | } 687 | 688 | /// Install target in OUT_DIR. 689 | fn install(&mut self) -> PathBuf { 690 | let mut cmd = self.xmake_command(); 691 | 692 | let dst = self 693 | .out_dir 694 | .clone() 695 | .unwrap_or_else(|| PathBuf::from(getenv_unwrap("OUT_DIR"))); 696 | 697 | cmd.env("XMAKERS_INSTALL_DIR", dst.clone()); 698 | cmd.run_script("install.lua"); 699 | dst 700 | } 701 | 702 | fn get_build_info(&mut self) -> Option { 703 | let mut cmd = self.xmake_command(); 704 | 705 | if let Some(targets) = &self.targets { 706 | // :: is used to handle namespaces in xmake but it interferes with the env separator 707 | // on linux, so use a different separator 708 | cmd.env("XMAKERS_TARGETS", targets.replace("::", "||")); 709 | } 710 | 711 | if let Some(output) = cmd.run_script("build_info.lua") { 712 | return output.parse().ok(); 713 | } 714 | None 715 | } 716 | 717 | fn get_static_crt(&self) -> bool { 718 | return self.static_crt.unwrap_or_else(|| { 719 | let feature = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new()); 720 | if feature.contains("crt-static") { 721 | true 722 | } else { 723 | false 724 | } 725 | }); 726 | } 727 | 728 | // In case no runtimes has been set, get one 729 | fn get_runtimes(&mut self) -> Option { 730 | // These runtimes may not be the most appropriate for each platform, but 731 | // taken the GNU standard libary is the most common one on linux, and same for 732 | // the clang equivalent on android. 733 | // TODO Explore which runtimes is more approriate for macosx 734 | let static_crt = self.get_static_crt(); 735 | let platform = self.get_xmake_plat(); 736 | 737 | let kind = match static_crt { 738 | true => "static", 739 | false => "shared", 740 | }; 741 | 742 | match platform.as_str() { 743 | "linux" => Some(format!("stdc++_{}", kind)), 744 | "android" => Some(format!("c++_{}", kind)), 745 | "windows" => { 746 | let msvc_runtime = if static_crt { "MT" } else { "MD" }; 747 | Some(msvc_runtime.to_owned()) 748 | } 749 | _ => None, 750 | } 751 | } 752 | 753 | /// Convert rust platform to xmake one 754 | fn get_xmake_plat(&mut self) -> String { 755 | if let Some(ref plat) = self.cache.plat { 756 | return plat.clone(); 757 | } 758 | 759 | // List of xmake platform https://github.com/xmake-io/xmake/tree/master/xmake/platforms 760 | // Rust targets: https://doc.rust-lang.org/rustc/platform-support.html 761 | let plat = match self.getenv_os("CARGO_CFG_TARGET_OS").unwrap().as_str() { 762 | "windows" => Some("windows"), 763 | "linux" => Some("linux"), 764 | "android" => Some("android"), 765 | "androideabi" => Some("android"), 766 | "emscripten" => Some("wasm"), 767 | "macos" => Some("macosx"), 768 | "ios" => Some("iphoneos"), 769 | "tvos" => Some("appletvos"), 770 | "fuchsia" => None, 771 | "solaris" => None, 772 | _ if getenv_unwrap("CARGO_CFG_TARGET_FAMILY") == "wasm" => Some("wasm"), 773 | _ => Some("cross"), 774 | } 775 | .expect("unsupported rust target"); 776 | 777 | self.cache.plat = Some(plat.to_string()); 778 | self.cache.plat.clone().unwrap() 779 | } 780 | 781 | fn get_xmake_arch(&mut self) -> String { 782 | if let Some(ref arch) = self.cache.arch { 783 | return arch.clone(); 784 | } 785 | 786 | // List rust targets with rustc --print target-list 787 | let os = self.getenv_os("CARGO_CFG_TARGET_OS").unwrap(); 788 | let target_arch = self.getenv_os("CARGO_CFG_TARGET_ARCH").unwrap(); 789 | let plat = self.get_xmake_plat(); 790 | 791 | // From v2.9.9 (not released) onwards, XMake used arm64 instead of arm64-v8a 792 | let arm64_changes = self 793 | .cache 794 | .xmake_version 795 | .as_ref() 796 | .unwrap_or(&XMAKE_MINIMUM_VERSION) 797 | < &Version::new(2, 9, 9); 798 | 799 | let arch = match (plat.as_str(), target_arch.as_str()) { 800 | ("android", a) if os == "androideabi" => match a { 801 | "arm" => "armeabi", // TODO Check with cc-rs if it's true 802 | "armv7" => "armeabi-v7a", 803 | a => a, 804 | }, 805 | ("android", "aarch64") => "arm64-v8a", 806 | ("android", "i686") => "x86", 807 | ("linux", "loongarch64") => "loong64", 808 | // From v2.9.9 (not released) onwards, XMake used arm64 instead of arm64-v8a 809 | ("linux", "aarch64") if arm64_changes => "arm64-v8a", 810 | ("watchos", "arm64_32") => "armv7k", 811 | ("watchos", "armv7k") => "armv7k", 812 | ("iphoneos", "aarch64") => "arm64", 813 | ("macosx", "aarch64") => "arm64", 814 | ("windows", "i686") => "x86", 815 | (_, "aarch64") => "arm64", 816 | (_, "i686") => "i386", 817 | (_, a) => a, 818 | } 819 | .to_string(); 820 | 821 | self.cache.arch = Some(arch); 822 | self.cache.arch.clone().unwrap() 823 | } 824 | 825 | /// Return xmake mode or inferred from Rust's compilation profile. 826 | /// 827 | /// * if `opt-level=0` then `debug`, 828 | /// * if `opt-level={1,2,3}` and: 829 | /// * `debug=false` then `release` 830 | /// * otherwise `releasedbg` 831 | /// * if `opt-level={s,z}` then `minsizerel` 832 | fn get_mode(&self) -> &str { 833 | if let Some(profile) = self.mode.as_ref() { 834 | profile 835 | } else { 836 | #[derive(PartialEq)] 837 | enum RustProfile { 838 | Debug, 839 | Release, 840 | } 841 | #[derive(PartialEq, Debug)] 842 | enum OptLevel { 843 | Debug, 844 | Release, 845 | Size, 846 | } 847 | 848 | let rust_profile = match &getenv_unwrap("PROFILE")[..] { 849 | "debug" => RustProfile::Debug, 850 | "release" | "bench" => RustProfile::Release, 851 | unknown => { 852 | eprintln!( 853 | "Warning: unknown Rust profile={}; defaulting to a release build.", 854 | unknown 855 | ); 856 | RustProfile::Release 857 | } 858 | }; 859 | 860 | let opt_level = match &getenv_unwrap("OPT_LEVEL")[..] { 861 | "0" => OptLevel::Debug, 862 | "1" | "2" | "3" => OptLevel::Release, 863 | "s" | "z" => OptLevel::Size, 864 | unknown => { 865 | let default_opt_level = match rust_profile { 866 | RustProfile::Debug => OptLevel::Debug, 867 | RustProfile::Release => OptLevel::Release, 868 | }; 869 | eprintln!( 870 | "Warning: unknown opt-level={}; defaulting to a {:?} build.", 871 | unknown, default_opt_level 872 | ); 873 | default_opt_level 874 | } 875 | }; 876 | 877 | let debug_info: bool = match &getenv_unwrap("DEBUG")[..] { 878 | "false" => false, 879 | "true" => true, 880 | unknown => { 881 | eprintln!("Warning: unknown debug={}; defaulting to `true`.", unknown); 882 | true 883 | } 884 | }; 885 | 886 | match (opt_level, debug_info) { 887 | (OptLevel::Debug, _) => "debug", 888 | (OptLevel::Release, false) => "release", 889 | (OptLevel::Release, true) => "releasedbg", 890 | (OptLevel::Size, _) => "minsizerel", 891 | } 892 | } 893 | } 894 | 895 | fn check_version(&mut self) { 896 | let version = Version::from_command(); 897 | if version.is_none() { 898 | println!("cargo:warning=xmake version could not be determined, it might not work"); 899 | return; 900 | } 901 | 902 | let version = version.unwrap(); 903 | if version < XMAKE_MINIMUM_VERSION { 904 | panic!( 905 | "xmake version {:?} is too old, please update to at least {:?}", 906 | version, XMAKE_MINIMUM_VERSION 907 | ); 908 | } 909 | self.cache.xmake_version = Some(version); 910 | } 911 | 912 | fn xmake_command(&mut self) -> XmakeCommand { 913 | let mut cmd = XmakeCommand::new(); 914 | 915 | // Add envs 916 | for &(ref k, ref v) in self.env.iter().chain(&self.env) { 917 | cmd.env(k, v); 918 | } 919 | 920 | if self.verbose { 921 | cmd.verbose(true); 922 | } 923 | 924 | cmd.project_dir(self.path.as_path()); 925 | 926 | cmd 927 | } 928 | 929 | fn getenv_os(&mut self, v: &str) -> Option { 930 | if let Some(val) = self.cache.env.get(v) { 931 | return val.clone(); 932 | } 933 | 934 | let r = env::var(v).ok(); 935 | println!("{} = {:?}", v, r); 936 | self.cache.env.insert(v.to_string(), r.clone()); 937 | r 938 | } 939 | } 940 | 941 | trait CommaSeparated { 942 | fn as_comma_separated(self) -> String; 943 | } 944 | 945 | impl CommaSeparated for [&str; N] { 946 | fn as_comma_separated(self) -> String { 947 | self.join(",") 948 | } 949 | } 950 | 951 | impl CommaSeparated for Vec { 952 | fn as_comma_separated(self) -> String { 953 | self.join(",") 954 | } 955 | } 956 | 957 | impl CommaSeparated for Vec<&str> { 958 | fn as_comma_separated(self) -> String { 959 | self.join(",") 960 | } 961 | } 962 | 963 | impl CommaSeparated for String { 964 | fn as_comma_separated(self) -> String { 965 | self 966 | } 967 | } 968 | 969 | impl CommaSeparated for &str { 970 | fn as_comma_separated(self) -> String { 971 | self.to_string() 972 | } 973 | } 974 | 975 | /// Parses a string representation of a map of key-value pairs, where the values are 976 | /// separated by the '|' character. 977 | /// 978 | /// The input string is expected to be in the format "key:value1|value2|...|valueN", 979 | /// where the values are separated by the '|' character. Any empty values are 980 | /// filtered out. 981 | /// 982 | fn parse_info_pairs>(s: S) -> HashMap> { 983 | let str: String = s.as_ref().trim().to_string(); 984 | let mut map: HashMap> = HashMap::new(); 985 | 986 | for l in str.lines() { 987 | // Split between key values 988 | if let Some((key, values)) = l.split_once(":") { 989 | let v: Vec<_> = values 990 | .split('|') 991 | .map(|x| x.to_string()) 992 | .filter(|s| !s.is_empty()) 993 | .collect(); 994 | map.insert(key.to_string(), v); 995 | } 996 | } 997 | map 998 | } 999 | 1000 | fn subkeys_of>(map: &HashMap>, main_key: S) -> Vec<&str> { 1001 | let main_key = main_key.as_ref(); 1002 | let prefix = format!("{main_key}."); 1003 | map.keys().filter_map(|k| k.strip_prefix(&prefix)).collect() 1004 | } 1005 | 1006 | // This trait may be replaced by the unstable auto trait feature 1007 | // References: 1008 | // https://users.rust-lang.org/t/how-to-exclude-a-type-from-generic-trait-implementation/26156/9 1009 | // https://doc.rust-lang.org/beta/unstable-book/language-features/auto-traits.html 1010 | // https://doc.rust-lang.org/beta/unstable-book/language-features/negative-impls.html 1011 | trait DirectParse {} 1012 | 1013 | // Implement for all primitive types that should use the scalar implementation 1014 | impl DirectParse for bool {} 1015 | impl DirectParse for u32 {} 1016 | impl DirectParse for String {} 1017 | 1018 | trait ParseField { 1019 | fn parse_field>( 1020 | map: &HashMap>, 1021 | field: S, 1022 | ) -> Result; 1023 | } 1024 | 1025 | // Only implement for types that implement DirectParse 1026 | impl ParseField for T 1027 | where 1028 | T: FromStr + DirectParse, 1029 | { 1030 | fn parse_field>( 1031 | map: &HashMap>, 1032 | field: S, 1033 | ) -> Result { 1034 | let field = field.as_ref(); 1035 | let values = map.get(field).ok_or(ParsingError::MissingKey)?; 1036 | if values.len() > 1 { 1037 | return Err(ParsingError::MultipleValues); 1038 | } 1039 | 1040 | let parsed: Vec = values 1041 | .iter() 1042 | .map(|s| s.parse::().map_err(|_| ParsingError::ParseError)) 1043 | .collect::, ParsingError>>()?; 1044 | parsed.into_iter().next().ok_or(ParsingError::MissingKey) 1045 | } 1046 | } 1047 | 1048 | // Vector implementation remains unchanged 1049 | impl ParseField> for Vec 1050 | where 1051 | T: FromStr, 1052 | { 1053 | fn parse_field>( 1054 | map: &HashMap>, 1055 | field: S, 1056 | ) -> Result, ParsingError> { 1057 | let field = field.as_ref(); 1058 | let values = map.get(field).ok_or(ParsingError::MissingKey)?; 1059 | values 1060 | .iter() 1061 | .map(|s| s.parse::().map_err(|_| ParsingError::ParseError)) 1062 | .collect::, ParsingError>>() 1063 | } 1064 | } 1065 | 1066 | fn parse_field>( 1067 | map: &HashMap>, 1068 | field: S, 1069 | ) -> Result 1070 | where 1071 | T: ParseField, 1072 | { 1073 | T::parse_field(map, field) 1074 | } 1075 | 1076 | fn getenv_unwrap(v: &str) -> String { 1077 | match env::var(v) { 1078 | Ok(s) => s, 1079 | Err(..) => fail(&format!("environment variable `{}` not defined", v)), 1080 | } 1081 | } 1082 | 1083 | fn fail(s: &str) -> ! { 1084 | panic!("\n{}\n\nbuild script failed, must exit now", s) 1085 | } 1086 | 1087 | struct XmakeCommand { 1088 | verbose: bool, 1089 | diagnosis: bool, 1090 | raw_output: bool, 1091 | command: Command, 1092 | args: Vec, 1093 | task: Option, 1094 | project_dir: Option, 1095 | } 1096 | 1097 | impl XmakeCommand { 1098 | /// Create a new XmakeCommand instance. 1099 | fn new() -> Self { 1100 | let mut command = Command::new(Self::xmake_executable()); 1101 | command.env("XMAKE_THEME", "plain"); 1102 | Self { 1103 | verbose: false, 1104 | diagnosis: false, 1105 | raw_output: false, 1106 | task: None, 1107 | command: command, 1108 | args: Vec::new(), 1109 | project_dir: None, 1110 | } 1111 | } 1112 | 1113 | fn xmake_executable() -> String { 1114 | env::var("XMAKE").unwrap_or(String::from("xmake")) 1115 | } 1116 | 1117 | /// Same as [`Command::arg`] 1118 | pub fn arg>(&mut self, arg: S) -> &mut Self { 1119 | self.args.push(arg.as_ref().to_os_string()); 1120 | self 1121 | } 1122 | 1123 | /// Same as [`Command::env`] 1124 | pub fn env(&mut self, key: K, val: V) -> &mut Self 1125 | where 1126 | K: AsRef, 1127 | V: AsRef, 1128 | { 1129 | self.command.env(key, val); 1130 | self 1131 | } 1132 | 1133 | /// Enable/disable verbose mode of xmake (default is false). 1134 | /// Correspond to the -v flag. 1135 | pub fn verbose(&mut self, value: bool) -> &mut Self { 1136 | self.verbose = value; 1137 | self 1138 | } 1139 | 1140 | // Enable/disable diagnosis mode of xmake (default is false). 1141 | /// Correspond to the -D flag. 1142 | pub fn diagnosis(&mut self, value: bool) -> &mut Self { 1143 | self.diagnosis = value; 1144 | self 1145 | } 1146 | 1147 | /// Sets the xmake tasks to run. 1148 | pub fn task>(&mut self, task: S) -> &mut Self { 1149 | self.task = Some(task.into()); 1150 | self 1151 | } 1152 | 1153 | /// Sets the project directory. 1154 | pub fn project_dir>(&mut self, project_dir: P) -> &mut Self { 1155 | use crate::path_clean::PathClean; 1156 | self.project_dir = Some(project_dir.as_ref().to_path_buf().clean()); 1157 | self 1158 | } 1159 | 1160 | /// Controls whether to capture raw, unfiltered command output (default is false). 1161 | /// 1162 | /// When enabled (true): 1163 | /// - All command output is captured and returned 1164 | /// 1165 | /// When disabled (false, default): 1166 | /// - Only captures output between special markers (`__xmakers_start__` and `__xmakers_end__`) 1167 | /// - Filters out diagnostic and setup information 1168 | /// 1169 | /// This setting is passed to the [`run`] function to control output processing. 1170 | pub fn raw_output(&mut self, value: bool) -> &mut Self { 1171 | self.raw_output = value; 1172 | self 1173 | } 1174 | 1175 | /// Run the command and return the output as a string. 1176 | /// Alias of [`run`] 1177 | pub fn run(&mut self) -> Option { 1178 | if let Some(task) = &self.task { 1179 | self.command.arg(task); 1180 | } 1181 | 1182 | if self.verbose { 1183 | self.command.arg("-v"); 1184 | } 1185 | if self.diagnosis { 1186 | self.command.arg("-D"); 1187 | } 1188 | 1189 | if let Some(project_dir) = &self.project_dir { 1190 | // Project directory are evaluated like this: 1191 | // 1. The Given Command Argument 1192 | // 2. The Environment Variable: XMAKE_PROJECT_DIR 1193 | // 3. The Current Directory 1194 | // 1195 | // The env doesn't work here because it is global, so it breaks 1196 | // packages. Just to be sure set both argument and current directory. 1197 | let project_dir = project_dir.as_path(); 1198 | self.command.current_dir(project_dir); 1199 | self.command.arg("-P").arg(project_dir); 1200 | } 1201 | 1202 | for arg in &self.args { 1203 | self.command.arg(arg); 1204 | } 1205 | run(&mut self.command, "xmake", self.raw_output) 1206 | } 1207 | 1208 | /// Execute a lua script, located in the src folder of this crate. 1209 | /// Note that this method overide any previously configured taks to be `lua`. 1210 | pub fn run_script>(&mut self, script: S) -> Option { 1211 | let script = script.as_ref(); 1212 | 1213 | // Get absolute path to the crate root 1214 | let crate_root = Path::new(env!("CARGO_MANIFEST_DIR")); 1215 | let script_file = crate_root.join("src").join(script); 1216 | 1217 | // Script to execute are positional argument so always last 1218 | self.args.push(script_file.into()); 1219 | self.task("lua"); // For the task to be lua 1220 | 1221 | self.run() 1222 | } 1223 | } 1224 | 1225 | fn run(cmd: &mut Command, program: &str, raw_output: bool) -> Option { 1226 | println!("running: {:?}", cmd); 1227 | let mut child = match cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() { 1228 | Ok(child) => child, 1229 | Err(ref e) if e.kind() == ErrorKind::NotFound => { 1230 | fail(&format!( 1231 | "failed to execute command: {}\nis `{}` not installed?", 1232 | e, program 1233 | )); 1234 | } 1235 | Err(e) => fail(&format!("failed to execute command: {}", e)), 1236 | }; 1237 | 1238 | let mut output = String::new(); 1239 | let mut take_output = false; 1240 | 1241 | // Read stdout in real-time 1242 | if let Some(stdout) = child.stdout.take() { 1243 | let reader = BufReader::new(stdout); 1244 | for line in reader.lines() { 1245 | if let Ok(line) = line { 1246 | // Print stdout for logging 1247 | println!("{}", line); 1248 | 1249 | take_output &= !line.starts_with("__xmakers_start__"); 1250 | if take_output || raw_output { 1251 | output.push_str(line.as_str()); 1252 | output.push('\n'); 1253 | } 1254 | take_output |= line.starts_with("__xmakers_start__"); 1255 | } 1256 | } 1257 | } 1258 | 1259 | // Wait for the command to complete 1260 | let status = child.wait().expect("failed to wait on child process"); 1261 | 1262 | if !status.success() { 1263 | fail(&format!( 1264 | "command did not execute successfully, got: {}", 1265 | status 1266 | )); 1267 | } 1268 | 1269 | Some(output) 1270 | } 1271 | 1272 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 1273 | struct Version { 1274 | major: u32, 1275 | minor: u32, 1276 | patch: u32, 1277 | } 1278 | 1279 | impl Version { 1280 | const fn new(major: u32, minor: u32, patch: u32) -> Self { 1281 | Self { 1282 | major, 1283 | minor, 1284 | patch, 1285 | } 1286 | } 1287 | 1288 | fn parse(s: &str) -> Option { 1289 | // As of v2.9.5, the format of the version output is "xmake v2.9.5+dev.478972cd9, A cross-platform build utility based on Lua". 1290 | // ``` 1291 | // $ xmake --version 1292 | // xmake v2.9.8+HEAD.13fc39238, A cross-platform build utility based on Lua 1293 | // Copyright (C) 2015-present Ruki Wang, tboox.org, xmake.io 1294 | // _ 1295 | // __ ___ __ __ __ _| | ______ 1296 | // \ \/ / | \/ |/ _ | |/ / __ \ 1297 | // > < | \__/ | /_| | < ___/ 1298 | // /_/\_\_|_| |_|\__ \|_|\_\____| 1299 | // by ruki, xmake.io 1300 | // 1301 | // point_right Manual: https://xmake.io/#/getting_started 1302 | // pray Donate: https://xmake.io/#/sponsor 1303 | // ``` 1304 | let version = s.lines().next()?.strip_prefix("xmake v")?; 1305 | let mut parts = version.splitn(2, '+'); // split at the '+' to separate the version and commit 1306 | 1307 | let version_part = parts.next()?; 1308 | // Get commit and branch 1309 | // let commit_part = parts.next().unwrap_or(""); // if there's no commit part, use an empty string 1310 | // let mut commit_parts = commit_part.splitn(2, '.'); // split commit part to get branch and commit hash 1311 | // let branch = commit_parts.next().unwrap_or(""); 1312 | // let commit = commit_parts.next().unwrap_or(""); 1313 | 1314 | let mut digits = version_part.splitn(3, '.'); 1315 | let major = digits.next()?.parse::().ok()?; 1316 | let minor = digits.next()?.parse::().ok()?; 1317 | let patch = digits.next()?.parse::().ok()?; 1318 | 1319 | Some(Version::new(major, minor, patch)) 1320 | } 1321 | 1322 | fn from_command() -> Option { 1323 | let output = XmakeCommand::new() 1324 | .raw_output(true) 1325 | .arg("--version") 1326 | .run()?; 1327 | Self::parse(output.as_str()) 1328 | } 1329 | } 1330 | 1331 | mod path_clean { 1332 | // Taken form the path-clean crate. 1333 | // Crates.io: https://crates.io/crates/path-clean 1334 | // GitHub: https://github.com/danreeves/path-clean 1335 | 1336 | use std::path::{Component, Path, PathBuf}; 1337 | pub(super) trait PathClean { 1338 | fn clean(&self) -> PathBuf; 1339 | } 1340 | 1341 | impl PathClean for Path { 1342 | fn clean(&self) -> PathBuf { 1343 | clean(self) 1344 | } 1345 | } 1346 | 1347 | impl PathClean for PathBuf { 1348 | fn clean(&self) -> PathBuf { 1349 | clean(self) 1350 | } 1351 | } 1352 | 1353 | pub(super) fn clean

(path: P) -> PathBuf 1354 | where 1355 | P: AsRef, 1356 | { 1357 | let mut out = Vec::new(); 1358 | 1359 | for comp in path.as_ref().components() { 1360 | match comp { 1361 | Component::CurDir => (), 1362 | Component::ParentDir => match out.last() { 1363 | Some(Component::RootDir) => (), 1364 | Some(Component::Normal(_)) => { 1365 | out.pop(); 1366 | } 1367 | None 1368 | | Some(Component::CurDir) 1369 | | Some(Component::ParentDir) 1370 | | Some(Component::Prefix(_)) => out.push(comp), 1371 | }, 1372 | comp => out.push(comp), 1373 | } 1374 | } 1375 | 1376 | if !out.is_empty() { 1377 | out.iter().collect() 1378 | } else { 1379 | PathBuf::from(".") 1380 | } 1381 | } 1382 | } 1383 | 1384 | #[cfg(test)] 1385 | mod tests { 1386 | use std::{path::PathBuf, vec}; 1387 | 1388 | use crate::{ 1389 | parse_field, parse_info_pairs, subkeys_of, BuildInfo, Link, LinkKind, ParsingError, Source, 1390 | }; 1391 | 1392 | fn to_set(vec: Vec) -> std::collections::HashSet { 1393 | vec.into_iter().collect() 1394 | } 1395 | 1396 | #[test] 1397 | fn parse_line() { 1398 | let expected_values: Vec<_> = ["value1", "value2", "value3"].map(String::from).to_vec(); 1399 | let map = parse_info_pairs("key:value1|value2|value3"); 1400 | assert!(map.contains_key("key")); 1401 | assert_eq!(map["key"], expected_values); 1402 | } 1403 | 1404 | #[test] 1405 | fn parse_line_empty_values() { 1406 | let expected_values: Vec<_> = ["value1", "value2"].map(String::from).to_vec(); 1407 | let map = parse_info_pairs("key:value1||value2"); 1408 | assert!(map.contains_key("key")); 1409 | assert_eq!(map["key"], expected_values); 1410 | } 1411 | 1412 | #[test] 1413 | fn parse_field_multiple_values() { 1414 | let map = parse_info_pairs("key:value1|value2|value3"); 1415 | let result: Result = parse_field(&map, "key"); 1416 | assert!(map.contains_key("key")); 1417 | assert!(result.is_err()); 1418 | assert_eq!(result.err().unwrap(), ParsingError::MultipleValues); 1419 | } 1420 | 1421 | #[test] 1422 | fn parse_with_subkeys() { 1423 | let map = parse_info_pairs("main:value\nmain.subkey:value1|value2|value3\nmain.sub2:vv"); 1424 | let subkeys = to_set(subkeys_of(&map, "main")); 1425 | assert_eq!(subkeys, to_set(vec!["sub2", "subkey"])); 1426 | } 1427 | 1428 | #[test] 1429 | fn parse_build_info_missing_key() { 1430 | let mut s = String::new(); 1431 | s.push_str("linkdirs:path/to/libA|path/to/libB|path\\to\\libC\n"); 1432 | s.push_str("links:linkA/static|linkB/shared\n"); 1433 | 1434 | let build_info: Result = s.parse(); 1435 | assert!(build_info.is_err()); 1436 | assert_eq!(build_info.err().unwrap(), ParsingError::MissingKey); 1437 | } 1438 | 1439 | #[test] 1440 | fn parse_build_info_missing_kind() { 1441 | let mut s = String::new(); 1442 | s.push_str("cxx_used:true\n"); 1443 | s.push_str("stl_used:false\n"); 1444 | s.push_str("links:linkA|linkB\n"); 1445 | s.push_str("linkdirs:path/to/libA|path/to/libB|path\\to\\libC\n"); 1446 | 1447 | let build_info: Result = s.parse(); 1448 | assert!(build_info.is_err()); 1449 | 1450 | // For now the returned error is not MalformedLink because map_err in parse_field shallow 1451 | // all the errors which are converted to ParsingError::ParseError 1452 | // assert_eq!(build_info.err().unwrap(), ParsingError::MalformedLink); 1453 | } 1454 | 1455 | #[test] 1456 | fn parse_build_info_missing_info() { 1457 | let mut s = String::new(); 1458 | s.push_str("links:linkA/static|linkB/shared\n"); 1459 | s.push_str("linkdirs:path/to/libA|path/to/libB|path\\to\\libC\n"); 1460 | 1461 | let build_info: Result = s.parse(); 1462 | assert!(build_info.is_err()); 1463 | } 1464 | 1465 | #[test] 1466 | fn parse_build_info() { 1467 | let expected_links = [ 1468 | Link::new("linkA", LinkKind::Static), 1469 | Link::new("linkB", LinkKind::Dynamic), 1470 | ]; 1471 | let expected_directories = ["path/to/libA", "path/to/libB", "path\\to\\libC"] 1472 | .map(PathBuf::from) 1473 | .to_vec(); 1474 | 1475 | let expected_includedirs_package_a = to_set(["includedir/a", "includedir\\aa"] 1476 | .map(PathBuf::from) 1477 | .to_vec()); 1478 | let expected_includedirs_package_b = to_set(["includedir/bb", "includedir\\b"] 1479 | .map(PathBuf::from) 1480 | .to_vec()); 1481 | 1482 | let expected_includedirs_target_c = to_set(["includedir/c"].map(PathBuf::from).to_vec()); 1483 | 1484 | let expected_includedirs_both_greedy = to_set([ 1485 | "includedir/c", 1486 | "includedir/bb", 1487 | "includedir\\b", 1488 | "includedir/a", 1489 | "includedir\\aa", 1490 | ] 1491 | .map(PathBuf::from) 1492 | .to_vec()); 1493 | 1494 | let expected_cxx = true; 1495 | let expected_stl = false; 1496 | 1497 | let mut s = String::new(); 1498 | s.push_str("cxx_used:true\n"); 1499 | s.push_str("stl_used:false\n"); 1500 | s.push_str("links:linkA/static|linkB/shared\n"); 1501 | s.push_str("linkdirs:path/to/libA|path/to/libB|path\\to\\libC\n"); 1502 | s.push_str("includedirs_package.a:includedir/a|includedir\\aa\n"); 1503 | s.push_str("includedirs_package.b:includedir/bb|includedir\\b\n"); 1504 | s.push_str("includedirs_target.c:includedir/c"); 1505 | 1506 | let build_info: BuildInfo = s.parse().unwrap(); 1507 | 1508 | assert_eq!(build_info.links(), &expected_links); 1509 | assert_eq!(build_info.linkdirs(), &expected_directories); 1510 | assert_eq!(build_info.use_cxx(), expected_cxx); 1511 | assert_eq!(build_info.use_stl(), expected_stl); 1512 | 1513 | assert_eq!( 1514 | to_set(build_info.includedirs(Source::Package, "a")), 1515 | expected_includedirs_package_a 1516 | ); 1517 | assert_eq!( 1518 | to_set(build_info.includedirs(Source::Package, "b")), 1519 | expected_includedirs_package_b 1520 | ); 1521 | assert_eq!( 1522 | to_set(build_info.includedirs(Source::Target, "c")), 1523 | expected_includedirs_target_c 1524 | ); 1525 | assert_eq!( 1526 | to_set(build_info.includedirs(Source::Both, "*")), 1527 | expected_includedirs_both_greedy 1528 | ); 1529 | } 1530 | } 1531 | -------------------------------------------------------------------------------- /src/modules/builder.lua: -------------------------------------------------------------------------------- 1 | -- Licensed under the Apache License, Version 2.0 (the "License"); 2 | -- you may not use this file except in compliance with the License. 3 | -- You may obtain a copy of the License at 4 | -- 5 | -- http://www.apache.org/licenses/LICENSE-2.0 6 | -- 7 | -- Unless required by applicable law or agreed to in writing, software 8 | -- distributed under the License is distributed on an "AS IS" BASIS, 9 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | -- See the License for the specific language governing permissions and 11 | -- limitations under the License. 12 | -- 13 | -- Copyright (C) 2015-present, TBOOX Open Source Group. 14 | -- 15 | 16 | import("core.base.graph") 17 | import("core.base.hashset") 18 | import("core.project.config") 19 | import("core.platform.platform") 20 | 21 | -- source: https://github.com/xmake-io/xmake/blob/master/xmake/core/tool/builder.lua 22 | 23 | -- builder: get the extra configuration from value 24 | function _extraconf(extras, value) 25 | local extra = extras 26 | if extra then 27 | if type(value) == "table" then 28 | extra = extra[table.concat(value, "_")] 29 | else 30 | extra = extra[value] 31 | end 32 | end 33 | return extra 34 | end 35 | 36 | -- builder: add items from config 37 | function _add_items_from_config(items, name, opt) 38 | local values = config.get(name) 39 | if values and name:endswith("dirs") then 40 | values = path.splitenv(values) 41 | end 42 | if values then 43 | table.insert(items, { 44 | name = name, 45 | values = table.wrap(values), 46 | check = opt.check, 47 | multival = opt.multival, 48 | mapper = opt.mapper}) 49 | end 50 | end 51 | 52 | -- builder: add items from toolchain 53 | function _add_items_from_toolchain(items, name, opt) 54 | local values 55 | local target = opt.target 56 | if target and target:type() == "target" then 57 | values = target:toolconfig(name) 58 | else 59 | values = platform.toolconfig(name) 60 | end 61 | if values then 62 | table.insert(items, { 63 | name = name, 64 | values = table.wrap(values), 65 | check = opt.check, 66 | multival = opt.multival, 67 | mapper = opt.mapper}) 68 | end 69 | end 70 | 71 | -- builder: add items from option 72 | function _add_items_from_option(items, name, opt) 73 | local values 74 | local target = opt.target 75 | if target then 76 | values = target:get(name) 77 | end 78 | if values then 79 | table.insert(items, { 80 | name = name, 81 | values = table.wrap(values), 82 | check = opt.check, 83 | multival = opt.multival, 84 | mapper = opt.mapper}) 85 | end 86 | end 87 | 88 | -- builder: add items from target 89 | function _add_items_from_target(items, name, opt) 90 | local target = opt.target 91 | if target then 92 | local result, sources = target:get_from(name, "*") 93 | if result then 94 | for idx, values in ipairs(result) do 95 | local source = sources[idx] 96 | local extras = target:extraconf_from(name, source) 97 | values = table.wrap(values) 98 | if values and #values > 0 then 99 | table.insert(items, { 100 | name = name, 101 | values = values, 102 | extras = extras, 103 | check = opt.check, 104 | multival = opt.multival, 105 | mapper = opt.mapper}) 106 | end 107 | end 108 | end 109 | end 110 | end 111 | 112 | -- builder: sort links of items 113 | function _sort_links_of_items(items, opt) 114 | opt = opt or {} 115 | local sortlinks = false 116 | local makegroups = false 117 | local linkorders = table.wrap(opt.linkorders) 118 | if #linkorders > 0 then 119 | sortlinks = true 120 | end 121 | local linkgroups = table.wrap(opt.linkgroups) 122 | local linkgroups_set = hashset.new() 123 | if #linkgroups > 0 then 124 | makegroups = true 125 | for _, linkgroup in ipairs(linkgroups) do 126 | for _, link in ipairs(linkgroup) do 127 | linkgroups_set:insert(link) 128 | end 129 | end 130 | end 131 | 132 | -- get all links 133 | local links = {} 134 | local linkgroups_map = {} 135 | local extras_map = {} 136 | local link_mapper 137 | local framework_mapper 138 | local linkgroup_mapper 139 | if sortlinks or makegroups then 140 | local linkitems = {} 141 | table.remove_if(items, function (_, item) 142 | local name = item.name 143 | local removed = false 144 | if name == "links" or name == "syslinks" then 145 | link_mapper = item.mapper 146 | removed = true 147 | table.insert(linkitems, item) 148 | elseif name == "frameworks" then 149 | framework_mapper = item.mapper 150 | removed = true 151 | table.insert(linkitems, item) 152 | elseif name == "linkgroups" then 153 | linkgroup_mapper = item.mapper 154 | removed = true 155 | table.insert(linkitems, item) 156 | end 157 | return removed 158 | end) 159 | 160 | -- @note table.remove_if will traverse backwards, 161 | -- we need to fix the initial link order first to make sure the syslinks are in the correct order 162 | linkitems = table.reverse(linkitems) 163 | for _, item in ipairs(linkitems) do 164 | local name = item.name 165 | for _, value in ipairs(item.values) do 166 | if name == "links" or name == "syslinks" then 167 | if not linkgroups_set:has(value) then 168 | table.insert(links, value) 169 | end 170 | elseif name == "frameworks" then 171 | table.insert(links, "framework::" .. value) 172 | elseif name == "linkgroups" then 173 | local extras = item.extras 174 | local extra = _extraconf(extras, value) 175 | local key = extra and extra.name or tostring(value) 176 | table.insert(links, "linkgroup::" .. key) 177 | linkgroups_map[key] = value 178 | extras_map[key] = extras 179 | end 180 | end 181 | end 182 | 183 | links = table.reverse_unique(links) 184 | end 185 | 186 | -- sort sublinks 187 | if sortlinks then 188 | local gh = graph.new(true) 189 | local from 190 | local original_deps = {} 191 | for _, link in ipairs(links) do 192 | local to = link 193 | if from and to then 194 | original_deps[from] = to 195 | end 196 | from = to 197 | end 198 | -- we need remove cycle in original links 199 | -- e.g. 200 | -- 201 | -- case1: 202 | -- original_deps: a -> b -> c -> d -> e 203 | -- new deps: e -> b 204 | -- graph: a -> b -> c -> d e (remove d -> e, add d -> nil) 205 | -- /|\ | 206 | -- -------------- 207 | -- 208 | -- case2: 209 | -- original_deps: a -> b -> c -> d -> e 210 | -- new deps: b -> a 211 | -- 212 | -- --------- 213 | -- | \|/ 214 | -- graph: a b -> c -> d -> e (remove a -> b, add a -> c) 215 | -- /|\ | 216 | -- ---- 217 | -- 218 | local function remove_cycle_in_original_deps(f, t) 219 | local k 220 | local v = t 221 | while v ~= f do 222 | k = v 223 | v = original_deps[v] 224 | if v == nil then 225 | break 226 | end 227 | end 228 | if v == f and k ~= nil then 229 | -- break the original from node, link to next node 230 | -- e.g. 231 | -- case1: d -x-> e, d -> nil, k: d, f: e 232 | -- case2: a -x-> b, a -> c, k: a, f: b 233 | original_deps[k] = original_deps[f] 234 | end 235 | end 236 | local links_set = hashset.from(links) 237 | for _, linkorder in ipairs(linkorders) do 238 | local from 239 | for _, link in ipairs(linkorder) do 240 | if links_set:has(link) then 241 | local to = link 242 | if from and to then 243 | remove_cycle_in_original_deps(from, to) 244 | gh:add_edge(from, to) 245 | end 246 | from = to 247 | end 248 | end 249 | end 250 | for k, v in pairs(original_deps) do 251 | gh:add_edge(k, v) 252 | end 253 | if not gh:empty() then 254 | local cycle = gh:find_cycle() 255 | if cycle then 256 | utils.warning("cycle links found in add_linkorders(): %s", table.concat(cycle, " -> ")) 257 | end 258 | links = gh:topological_sort() 259 | end 260 | end 261 | 262 | -- re-generate links to items list 263 | if sortlinks or makegroups then 264 | for _, link in ipairs(links) do 265 | if link:startswith("framework::") then 266 | link = link:sub(12) 267 | table.insert(items, {name = "frameworks", values = table.wrap(link), check = false, multival = false, mapper = framework_mapper}) 268 | elseif link:startswith("linkgroup::") then 269 | local key = link:sub(12) 270 | local values = linkgroups_map[key] 271 | local extras = extras_map[key] 272 | table.insert(items, {name = "linkgroups", values = table.wrap(values), extras = extras, check = false, multival = false, mapper = linkgroup_mapper}) 273 | else 274 | table.insert(items, {name = "links", values = table.wrap(link), check = false, multival = false, mapper = link_mapper}) 275 | end 276 | end 277 | end 278 | end 279 | 280 | -- get the links in the correct order 281 | function orderlinks(target) 282 | assert(target:is_binary(), "linkorders() requires a binary target") 283 | local linkorders = {} 284 | local linkgroups = {} 285 | 286 | local values = target:get_from("linkorders", "*") 287 | if values then 288 | for _, value in ipairs(values) do 289 | table.join2(linkorders, value) 290 | end 291 | end 292 | values = target:get_from("linkgroups", "*") 293 | if values then 294 | for _, value in ipairs(values) do 295 | table.join2(linkgroups, value) 296 | end 297 | end 298 | 299 | local items = {} 300 | 301 | _add_items_from_target(items, "linkgroups", {target = target}) 302 | 303 | _add_items_from_config(items, "links", {target = target}) 304 | _add_items_from_target(items, "links", {target = target}) 305 | _add_items_from_option(items, "links", {target = target}) 306 | _add_items_from_toolchain(items, "links", {target = target}) 307 | 308 | _add_items_from_config(items, "frameworks", {target = target}) 309 | _add_items_from_target(items, "frameworks", {target = target}) 310 | _add_items_from_option(items, "frameworks", {target = target}) 311 | _add_items_from_toolchain(items, "frameworks", {target = target}) 312 | 313 | _add_items_from_config(items, "syslinks", {target = target}) 314 | _add_items_from_target(items, "syslinks", {target = target}) 315 | _add_items_from_option(items, "syslinks", {target = target}) 316 | _add_items_from_toolchain(items, "syslinks", {target = target}) 317 | 318 | 319 | if #linkorders > 0 or #linkgroups > 0 then 320 | _sort_links_of_items(items, {linkorders = linkorders, linkgroups = linkgroups}) 321 | end 322 | return items 323 | end -------------------------------------------------------------------------------- /src/modules/include_scanner/clang_gcc.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2024 A2va 2 | 3 | -- Permission is hereby granted, free of charge, to any 4 | -- person obtaining a copy of this software and associated 5 | -- documentation files (the "Software"), to deal in the 6 | -- Software without restriction, including without 7 | -- limitation the rights to use, copy, modify, merge, 8 | -- publish, distribute, sublicense, and/or sell copies of 9 | -- the Software, and to permit persons to whom the Software 10 | -- is furnished to do so, subject to the following 11 | -- conditions: 12 | 13 | -- The above copyright notice and this permission notice 14 | -- shall be included in all copies or substantial portions 15 | -- of the Software. 16 | 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | -- DEALINGS IN THE SOFTWARE. 26 | 27 | function _extract_includes(content) 28 | local includes = {} 29 | for line in string.gmatch(content, "[^\r\n]+") do 30 | -- extract file paths from the lines that contain include files 31 | for include in string.gmatch(line, "/[%a%d%p\\]+") do 32 | table.insert(includes, path.normalize(include)) 33 | end 34 | end 35 | -- remove the source file itself 36 | table.remove(includes, 1) 37 | return includes 38 | end 39 | 40 | function scan(target, sourcefile, opt) 41 | local compinst = target:compiler("cxx") 42 | local ifile = path.translate(path.join(outputdir, sourcefile .. ".i")) 43 | 44 | local compflags = compinst:compflags({sourcefile = sourcefile, target = target}) 45 | local flags = table.join(compflags, {"-M", "-c", "-x", "c++", sourcefile}) 46 | 47 | local content, _ = os.iorunv(compinst:program(), flags) 48 | return _extract_includes(content) 49 | end -------------------------------------------------------------------------------- /src/modules/include_scanner/msvc.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2024 A2va 2 | 3 | -- Permission is hereby granted, free of charge, to any 4 | -- person obtaining a copy of this software and associated 5 | -- documentation files (the "Software"), to deal in the 6 | -- Software without restriction, including without 7 | -- limitation the rights to use, copy, modify, merge, 8 | -- publish, distribute, sublicense, and/or sell copies of 9 | -- the Software, and to permit persons to whom the Software 10 | -- is furnished to do so, subject to the following 11 | -- conditions: 12 | 13 | -- The above copyright notice and this permission notice 14 | -- shall be included in all copies or substantial portions 15 | -- of the Software. 16 | 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | -- DEALINGS IN THE SOFTWARE. 26 | 27 | import("rules.c++.modules.modules_support.stl_headers", {rootdir = os.programdir()}) 28 | import("modules.private.tools.cl.parse_include", {rootdir = os.programdir()}) 29 | 30 | function _extract_includes(content) 31 | local includes = {} 32 | for line in string.gmatch(content, "[^\r\n]+") do 33 | local include = parse_include(line) 34 | if include then 35 | table.insert(includes, include) 36 | end 37 | end 38 | 39 | return includes 40 | end 41 | 42 | function scan(target, sourcefile, opt) 43 | local compinst = target:compiler("cxx") 44 | local msvc = target:toolchain("msvc") 45 | 46 | local compflags = compinst:compflags({sourcefile = sourcefile, target = target}) 47 | local flags = table.join(compflags, {"/showIncludes", "/c", "-TP", sourcefile}) 48 | 49 | local content, _ = os.iorunv(compinst:program(), flags, {envs = msvc:runenvs()}) 50 | 51 | return _extract_includes(content) 52 | end -------------------------------------------------------------------------------- /src/modules/utils.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Copyright (c) 2024 A2va 3 | 4 | -- Permission is hereby granted, free of charge, to any 5 | -- person obtaining a copy of this software and associated 6 | -- documentation files (the "Software"), to deal in the 7 | -- Software without restriction, including without 8 | -- limitation the rights to use, copy, modify, merge, 9 | -- publish, distribute, sublicense, and/or sell copies of 10 | -- the Software, and to permit persons to whom the Software 11 | -- is furnished to do so, subject to the following 12 | -- conditions: 13 | 14 | -- The above copyright notice and this permission notice 15 | -- shall be included in all copies or substantial portions 16 | -- of the Software. 17 | 18 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | -- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | -- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | -- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | -- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | -- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | -- DEALINGS IN THE SOFTWARE. 27 | 28 | import("core.project.config") 29 | import("core.project.project") 30 | 31 | import("core.base.bytes") 32 | import("core.base.graph") 33 | import("core.base.hashset") 34 | import("core.cache.memcache") 35 | import("core.project.project") 36 | 37 | -- import("rules.c++.modules.stlheaders", {rootdir = os.programdir()}) 38 | 39 | -- The PR #6338(https://github.com/xmake-io/xmake/pull/6338) changed the location and name of stl_headers 40 | -- import and with try and test if was imported correctly. 41 | -- TODO: In xmake-rs v0.4 I will force on xmake v3 so this patch can removed. 42 | local stl_headers 43 | try { 44 | function () 45 | stl_headers = import("rules.c++.modules.modules_support.stl_headers", {rootdir = os.programdir()}) 46 | end, 47 | catch 48 | { 49 | function (errors) 50 | stl_headers = import("rules.c++.modules.stlheaders", {rootdir = os.programdir()}) 51 | end 52 | } 53 | } 54 | assert(stl_headers, "stl_headers is nil") 55 | 56 | function _hashset_join(self, ...) 57 | for _, h in ipairs({...}) do 58 | for v, _ in pairs(h:data()) do 59 | self:insert(v) 60 | end 61 | end 62 | return self 63 | end 64 | 65 | -- source: https://github.com/xmake-io/xmake/blob/dev/xmake/rules/c%2B%2B/modules/modules_support/compiler_support.lua 66 | function _compiler_support(target) 67 | local memcache = memcache.cache("compiler_support") 68 | local cachekey = tostring(target) 69 | local compiler_support = memcache:get2("compiler_support", cachekey) 70 | if compiler_support == nil and xmake.version():lt("3.0.0") then 71 | local rootdir = path.join(os.programdir(), "rules", "c++", "modules", "modules_support") 72 | if target:has_tool("cxx", "clang", "clangxx") then 73 | compiler_support = import("clang.compiler_support", {anonymous = true, rootdir = rootdir}) 74 | elseif target:has_tool("cxx", "gcc", "gxx") then 75 | compiler_support = import("gcc.compiler_support", {anonymous = true, rootdir = rootdir}) 76 | elseif target:has_tool("cxx", "cl") then 77 | compiler_support = import("msvc.compiler_support", {anonymous = true, rootdir = rootdir}) 78 | else 79 | local _, toolname = target:tool("cxx") 80 | raise("compiler(%s): does not support c++ module!", toolname) 81 | end 82 | 83 | memcache:set2("compiler_support", cachekey, compiler_support) 84 | end 85 | 86 | -- TODO Remove in xmake-rs v0.4 87 | if compiler_support == nil and xmake.version():ge("3.0.0") then 88 | local rootdir = path.join(os.programdir(), "rules", "c++", "modules") 89 | if target:has_tool("cxx", "clang", "clangxx") then 90 | compiler_support = import("clang.support", {anonymous = true, rootdir = rootdir}) 91 | elseif target:has_tool("cxx", "gcc", "gxx") then 92 | compiler_support = import("gcc.support", {anonymous = true, rootdir = rootdir}) 93 | elseif target:has_tool("cxx", "cl") then 94 | compiler_support = import("msvc.support", {anonymous = true, rootdir = rootdir}) 95 | else 96 | local _, toolname = target:tool("cxx") 97 | raise("compiler(%s): does not support c++ module!", toolname) 98 | end 99 | 100 | memcache:set2("compiler_support", cachekey, compiler_support) 101 | end 102 | 103 | return compiler_support 104 | end 105 | 106 | -- return the available targets 107 | -- the target is available under the following conditions: 108 | -- the kind is either shared or static 109 | -- the target has no deps or it's the last target in the deps chain 110 | -- opt.targets: list of predifined targets 111 | function _get_available_targets(opt) 112 | local opt = opt or {} 113 | local gh = graph.new(true) 114 | local set = hashset.new() 115 | 116 | local map = function(index, target) 117 | return project.target(target) 118 | end 119 | 120 | local targets = opt.targets and table.imap(opt.targets, map) or table.values(project.targets()) 121 | assert(#targets > 0, "some targets are not found!") 122 | 123 | local memcache = memcache.cache("utils.get_available_targets") 124 | local cachekey = get_cache_key(targets) 125 | 126 | local cache = memcache:get2("utils.get_available_targets", cachekey) 127 | if cache then 128 | return cache.targets, cache.targetsname 129 | end 130 | 131 | for _, target in pairs(targets) do 132 | -- ignore non enabled target and other than static or shared 133 | local enabled = target:get("enabled") or true 134 | if not (target:is_shared() or target:is_static() or target:is_phony()) or not enabled then 135 | goto continue 136 | end 137 | 138 | local name = get_namespace_target(target) 139 | 140 | local deps = target:get("deps") 141 | for _, dep in ipairs(deps) do 142 | gh:add_edge(name, dep) 143 | end 144 | if not deps then 145 | set:insert(name) 146 | end 147 | 148 | ::continue:: 149 | end 150 | 151 | local parents = hashset.new() 152 | local children = hashset.new() 153 | 154 | for _, edge in ipairs(graph:edges()) do 155 | parents:insert(edge:from()) 156 | children:insert(edge:to()) 157 | end 158 | 159 | for _, child in children:keys() do 160 | set:remove(child) 161 | end 162 | 163 | local targets = {} 164 | local targetsname = {} 165 | 166 | local result = _hashset_join(set, parents) 167 | for _, target in result:orderkeys() do 168 | table.insert(targetsname, target) 169 | table.insert(targets, project.target(target)) 170 | end 171 | 172 | memcache:set("utils.get_available_targets", cachekey, {targets = targets, targetsname = targetsname}) 173 | return targets, targetsname 174 | end 175 | 176 | -- get the targets 177 | function get_targets() 178 | local list = _g.targets_list 179 | if list == nil then 180 | local env = os.getenv("XMAKERS_TARGETS") 181 | local values = (env ~= "") and env or nil 182 | if values then 183 | values = string.gsub(values, "||", "::") 184 | values = table.wrap(string.split(values, ",")) 185 | end 186 | local targets, targetsname = _get_available_targets({targets = values}) 187 | list = {targets, targetsname} 188 | _g.targets_list = list 189 | end 190 | 191 | return list[1], list[2] 192 | end 193 | 194 | -- get a cache key for the given targets 195 | function get_cache_key(targets) 196 | local targets = targets or get_targets() 197 | 198 | local key = {} 199 | for _, target in ipairs(targets) do 200 | table.insert(key, target:name()) 201 | end 202 | return table.concat(key, "-") 203 | end 204 | 205 | -- create a binary target, that depends on all given targets 206 | function create_binary_target(targets) 207 | 208 | -- take the first target as the fake target 209 | local fake_target = targets[1]:clone() 210 | local cachekey = get_cache_key(targets) 211 | local hashed_key = hash.sha256(bytes(cachekey)) 212 | fake_target:name_set("xmake-rs-" .. string.sub(hashed_key, 1, 8)) 213 | fake_target:data_set("xmakers-cachekey", cachekey) 214 | fake_target:set("kind", "binary") 215 | 216 | -- reset some info 217 | fake_target:set("deps", nil) 218 | fake_target:set("packages", nil) 219 | fake_target:set("rules", nil) 220 | fake_target:set("links", nil) 221 | fake_target:set("syslinks", nil) 222 | fake_target:set("frameworks", nil) 223 | fake_target:set("linkdirs", nil) 224 | fake_target:set("runenvs", nil) 225 | 226 | for _, target in ipairs(targets) do 227 | fake_target:add("deps", target:name()) 228 | end 229 | 230 | -- normally this method is already present in the xmake codebase 231 | -- but the opt.interface is set to true which is not what I want, so I override it 232 | fake_target.pkgenvs = function(self) 233 | local pkgenvs = self._PKGENVS 234 | if pkgenvs == nil then 235 | local pkgs = hashset.new() 236 | for _, pkgname in ipairs(table.wrap(self:get("packages"))) do 237 | local pkg = self:pkg(pkgname) 238 | if pkg then 239 | pkgs:insert(pkg) 240 | end 241 | end 242 | -- we can also get package envs from deps (public package) 243 | -- @see https://github.com/xmake-io/xmake/issues/2729 244 | for _, dep in ipairs(self:orderdeps()) do 245 | for _, pkgname in ipairs(table.wrap(dep:get("packages", {interface = false}))) do 246 | local pkg = dep:pkg(pkgname) 247 | if pkg then 248 | pkgs:insert(pkg) 249 | end 250 | end 251 | end 252 | for _, pkg in pkgs:orderkeys() do 253 | local envs = pkg:get("envs") 254 | if envs then 255 | for name, values in table.orderpairs(envs) do 256 | if type(values) == "table" then 257 | values = path.joinenv(values) 258 | end 259 | pkgenvs = pkgenvs or {} 260 | if pkgenvs[name] then 261 | pkgenvs[name] = pkgenvs[name] .. path.envsep() .. values 262 | else 263 | pkgenvs[name] = values 264 | end 265 | end 266 | end 267 | end 268 | self._PKGENVS = pkgenvs or false 269 | end 270 | return pkgenvs or nil 271 | end 272 | 273 | project.target_add(fake_target) 274 | 275 | -- load the newly made target 276 | config.load() 277 | project.load_targets() 278 | 279 | return fake_target 280 | end 281 | 282 | -- retrieves a value from the specified target, using the given name and scope. 283 | -- unpack the multiple return values into a single table. 284 | function get_from_target(target, name, scope) 285 | local result, _ = target:get_from(name, scope) 286 | result = result or {} 287 | result = table.join(table.unpack(result)) 288 | return table.wrap(result) 289 | end 290 | 291 | -- get the the true target name with namespace 292 | function get_namespace_target(target) 293 | -- TODO With xmake v3 replace by target:fullname() 294 | if (not is_namespace_supported()) and (target:namespace() ~= nil) then 295 | raise("target(%s): target is in a namespace, but xmake is neither in v3 nor the compatibility.version policy was set.", name) 296 | end 297 | local name = target:name() 298 | if is_namespace_supported() and target:namespace() then 299 | name = target:namespace() .. "::" .. name 300 | end 301 | return name 302 | end 303 | 304 | --- check if the given target uses the C++ standard library (STL) based on the provided include directories. 305 | --- opt.strict: if true, the include directory must exactly match the STL include directory 306 | function is_stl_used(target, includes, opt) 307 | opt = opt or {} 308 | local stl_includedirs = _compiler_support(target).toolchain_includedirs(target) 309 | local std_used = false 310 | 311 | -- TODO 312 | local is_stl_header = stl_headers.is_stl_header or stl_headers.is_stlheader 313 | 314 | for _, include in ipairs(includes) do 315 | for _, stl_includedir in ipairs(stl_includedirs) do 316 | local file = path.relative(include, stl_includedir) 317 | 318 | local includedirs_check = opt.strict and include:startswith(stl_includedir) or true 319 | if includedirs_check and is_stl_header(file) then 320 | std_used = true 321 | end 322 | end 323 | 324 | if std_used then 325 | break 326 | end 327 | end 328 | 329 | return std_used 330 | end 331 | 332 | -- check if namespace are supported 333 | function is_namespace_supported() 334 | local is_supported = _g.is_namespace_supported 335 | if is_supported == nil then 336 | is_supported = xmake.version():ge("3.0.0") or (xmake.version():satisfies(">=2.9.8 <3.0.0") and (project.policy("compatibility.version") == "3.0")) 337 | _g.is_namespace_supported = is_supported 338 | end 339 | return is_supported 340 | end -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .xmake/ -------------------------------------------------------------------------------- /tests/check-mode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "check-mode" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../.." } 10 | -------------------------------------------------------------------------------- /tests/check-mode/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | use std::env; 4 | fn main() { 5 | // Force release mode 6 | env::set_var("PROFILE", "release"); 7 | env::set_var("OPT_LEVEL", "3"); 8 | env::set_var("DEBUG", "false"); 9 | xmake::build("."); 10 | } -------------------------------------------------------------------------------- /tests/check-mode/src/foo.c: -------------------------------------------------------------------------------- 1 | int mode() { 2 | #ifdef FOO_DEBUG 3 | return 1; 4 | #elif FOO_RELEASE 5 | return 2; 6 | #else 7 | return 0; 8 | #endif 9 | } 10 | -------------------------------------------------------------------------------- /tests/check-mode/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn mode() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { mode() }; 9 | assert!(output == 2); 10 | } 11 | -------------------------------------------------------------------------------- /tests/check-mode/xmake.lua: -------------------------------------------------------------------------------- 1 | target("foo") 2 | set_kind("static") 3 | add_files("src/foo.c") 4 | if is_mode("debug") then 5 | add_defines("FOO_DEBUG") 6 | elseif is_mode("release") then 7 | add_defines("FOO_RELEASE") 8 | end -------------------------------------------------------------------------------- /tests/cxx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cxx" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../.." } 10 | -------------------------------------------------------------------------------- /tests/cxx/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/cxx/src/foo.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | int add(int a, int b); 6 | 7 | #ifdef __cplusplus 8 | } 9 | #endif 10 | 11 | #include 12 | 13 | int add(int a, int b) { 14 | std::vector v; 15 | v.push_back(a); 16 | v.push_back(b); 17 | return v.at(0) + v.at(1); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /tests/cxx/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn add(a: libc::c_int, b: libc::c_int) -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { add(4, 4) }; 9 | assert!(output == 8, "4 + 4 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/cxx/xmake.lua: -------------------------------------------------------------------------------- 1 | target("foo") 2 | set_kind("static") 3 | add_files("src/foo.cpp") -------------------------------------------------------------------------------- /tests/namespace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "namespace" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../.." } 10 | -------------------------------------------------------------------------------- /tests/namespace/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | use xmake::Config; 3 | 4 | fn main() { 5 | Config::new(".").targets("ns1::foo").build(); 6 | } -------------------------------------------------------------------------------- /tests/namespace/src/foo.c: -------------------------------------------------------------------------------- 1 | int add(int a, int b) { 2 | return a + b; 3 | } -------------------------------------------------------------------------------- /tests/namespace/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn add(a: libc::c_int, b: libc::c_int) -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { add(4, 4) }; 9 | assert!(output == 8, "4 + 4 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/namespace/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("compatibility.version", "3.0") 2 | 3 | namespace("ns1", function () 4 | target("foo") 5 | set_kind("static") 6 | add_files("src/foo.c") 7 | end) -------------------------------------------------------------------------------- /tests/packages/README.md: -------------------------------------------------------------------------------- 1 | The test naming in this folder is a folllows: 2 | - `stt`: static targert 3 | - `sht`: shared target 4 | - `stf`: static foo (package name) 5 | - `shf`: shared foo (package name) 6 | - `stb`: static bar 7 | - `shb`: shared bar 8 | 9 | So a test name like: `stt-shf-stb`, test in the following configuration: static target, shared foo, static bar. 10 | 11 | For these tests to work correctly policy `package.librarydeps.strict_compatibility` had to be enabled, because otherwise the bar shared libraries in the example will referenced foo even in the second snippet because the package hash of bar would be the same. 12 | ```lua 13 | add_requires("xmrs-bar", {configs = {shared = true}}) 14 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = true}}) 15 | ``` 16 | And another that only requires bar. 17 | ```lua 18 | add_requires("xmrs-bar", {configs = {shared = true}}) 19 | ``` -------------------------------------------------------------------------------- /tests/packages/sht-shf-shb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sht-shf-shb" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../../.." } 10 | -------------------------------------------------------------------------------- /tests/packages/sht-shf-shb/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/packages/sht-shf-shb/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn target() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { target() }; 9 | assert!(output == 789, "789 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/packages/sht-shf-shb/src/target.c: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | 5 | #ifndef TARGET_STATIC 6 | #ifdef _WIN32 7 | #define TARGET_DLL_EXPORT __declspec(dllexport) 8 | #define TARGET_DLL_IMPORT __declspec(dllimport) 9 | #else 10 | #define TARGET_DLL_EXPORT [[gnu::visibility("default")]] 11 | #define TARGET_DLL_IMPORT [[gnu::visibility("default")]] 12 | #endif 13 | #else 14 | #define TARGET_DLL_EXPORT 15 | #define TARGET_DLL_IMPORT 16 | #endif 17 | 18 | #ifdef TARGET_BUILD 19 | #define TARGET_PUBLIC_API TARGET_DLL_EXPORT 20 | #else 21 | #define TARGET_PUBLIC_API TARGET_DLL_IMPORT 22 | #endif 23 | 24 | TARGET_PUBLIC_API int target(); 25 | 26 | int target() { 27 | int b = bar(); 28 | assert(b == 456); 29 | return 789; 30 | } 31 | -------------------------------------------------------------------------------- /tests/packages/sht-shf-shb/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("package.librarydeps.strict_compatibility", true) 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | 4 | add_requires("xmrs-bar", {configs = {shared = true}}) 5 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = true}}) 6 | 7 | target("target") 8 | set_kind("shared") 9 | add_files("src/target.c") 10 | add_packages("xmrs-bar") 11 | add_defines("TARGET_BUILD") -------------------------------------------------------------------------------- /tests/packages/stt-shf-shb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stt-shf-shb" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../../.." } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-shb/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/packages/stt-shf-shb/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn target() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { target() }; 9 | assert!(output == 789, "789 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-shb/src/target.c: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | 5 | int target() { 6 | int b = bar(); 7 | assert(b == 456); 8 | return 789; 9 | } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-shb/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("package.librarydeps.strict_compatibility", true) 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | 4 | add_requires("xmrs-bar", {configs = {shared = true}}) 5 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = true}}) 6 | 7 | target("target") 8 | set_kind("static") 9 | add_files("src/target.c") 10 | add_packages("xmrs-bar") -------------------------------------------------------------------------------- /tests/packages/stt-shf-stb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stt-shf-stb" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../../.." } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-stb/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/packages/stt-shf-stb/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn target() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { target() }; 9 | assert!(output == 789, "789 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-stb/src/target.c: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | 5 | int target() { 6 | int b = bar(); 7 | assert(b == 456); 8 | return 789; 9 | } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-shf-stb/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("package.librarydeps.strict_compatibility", true) 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | 4 | add_requires("xmrs-bar", {configs = {shared = false}}) 5 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = true}}) 6 | 7 | target("target") 8 | set_kind("static") 9 | add_files("src/target.c") 10 | add_packages("xmrs-bar") -------------------------------------------------------------------------------- /tests/packages/stt-stf-shb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stt-stf-shb" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../../.." } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-shb/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/packages/stt-stf-shb/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn target() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { target() }; 9 | assert!(output == 789, "789 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-shb/src/target.c: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | 5 | int target() { 6 | int b = bar(); 7 | assert(b == 456); 8 | return 789; 9 | } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-shb/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("package.librarydeps.strict_compatibility", true) 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | 4 | add_requires("xmrs-bar", {configs = {shared = true}}) 5 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = false}}) 6 | 7 | target("target") 8 | set_kind("static") 9 | add_files("src/target.c") 10 | add_packages("xmrs-bar") -------------------------------------------------------------------------------- /tests/packages/stt-stf-stb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stt-stf-stb" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../../.." } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-stb/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/packages/stt-stf-stb/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn target() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { target() }; 9 | assert!(output == 789, "789 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-stb/src/target.c: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | 5 | int target() { 6 | int b = bar(); 7 | assert(b == 456); 8 | return 789; 9 | } 10 | -------------------------------------------------------------------------------- /tests/packages/stt-stf-stb/xmake.lua: -------------------------------------------------------------------------------- 1 | set_policy("package.librarydeps.strict_compatibility", true) 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | 4 | add_requires("xmrs-bar", {configs = {shared = false}}) 5 | add_requireconfs("xmrs-bar.xmrs-foo", {configs = {shared = false}}) 6 | 7 | target("target") 8 | set_kind("static") 9 | add_files("src/target.c") 10 | add_packages("xmrs-bar") -------------------------------------------------------------------------------- /tests/phony/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phony" 3 | authors = ["A2va"] 4 | build = "build.rs" 5 | 6 | [dependencies] 7 | libc = "0.2.169" 8 | 9 | [build-dependencies] 10 | xmake = { path = "../.." } 11 | -------------------------------------------------------------------------------- /tests/phony/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | let mut config = xmake::Config::new("."); 5 | config.build(); 6 | 7 | let foo_includedirs = config.build_info().includedirs(xmake::Source::Package, "xmrs-foo"); 8 | assert_eq!(foo_includedirs.is_empty(), false); 9 | 10 | let f = foo_includedirs.first().unwrap(); 11 | assert!(f.join("foo").join("foo.h").exists()); 12 | } -------------------------------------------------------------------------------- /tests/phony/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn foo() -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { foo() }; 9 | assert!(output == 123, "123 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/phony/xmake.lua: -------------------------------------------------------------------------------- 1 | 2 | add_repositories("xmakers-repo https://github.com/A2va/xmakers-repo") 3 | add_requires("xmrs-foo") 4 | 5 | target("bar") 6 | set_kind("phony") 7 | add_packages("xmrs-foo", {public = true}) 8 | -------------------------------------------------------------------------------- /tests/shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../.." } 10 | -------------------------------------------------------------------------------- /tests/shared/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | use xmake::Config; 3 | 4 | fn main() { 5 | Config::new(".").targets("foo").build(); 6 | } -------------------------------------------------------------------------------- /tests/shared/src/foo.c: -------------------------------------------------------------------------------- 1 | #ifndef FOO_STATIC 2 | #ifdef _WIN32 3 | #define FOO_DLL_EXPORT __declspec(dllexport) 4 | #define FOO_DLL_IMPORT __declspec(dllimport) 5 | #else 6 | #define FOO_DLL_EXPORT [[gnu::visibility("default")]] 7 | #define FOO_DLL_IMPORT [[gnu::visibility("default")]] 8 | #endif 9 | #else 10 | #define FOO_DLL_EXPORT 11 | #define FOO_DLL_IMPORT 12 | #endif 13 | 14 | #ifdef FOO_BUILD 15 | #define FOO_PUBLIC_API FOO_DLL_EXPORT 16 | #else 17 | #define FOO_PUBLIC_API FOO_DLL_IMPORT 18 | #endif 19 | 20 | FOO_PUBLIC_API int add(); 21 | 22 | int add(int a, int b) { 23 | return a + b; 24 | } -------------------------------------------------------------------------------- /tests/shared/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn add(a: libc::c_int, b: libc::c_int) -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { add(4, 4) }; 9 | assert!(output == 8, "4 + 4 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/shared/xmake.lua: -------------------------------------------------------------------------------- 1 | target("foo") 2 | set_kind("shared") 3 | add_files("src/foo.c") 4 | add_defines("FOO_BUILD") -------------------------------------------------------------------------------- /tests/static/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static" 3 | authors = ["A2va"] 4 | 5 | [dependencies] 6 | libc = "0.2.169" 7 | 8 | [build-dependencies] 9 | xmake = { path = "../.." } 10 | -------------------------------------------------------------------------------- /tests/static/build.rs: -------------------------------------------------------------------------------- 1 | extern crate xmake; 2 | 3 | fn main() { 4 | xmake::build("."); 5 | } -------------------------------------------------------------------------------- /tests/static/src/foo.c: -------------------------------------------------------------------------------- 1 | int add(int a, int b) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /tests/static/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | extern "C" { 4 | fn add(a: libc::c_int, b: libc::c_int) -> libc::c_int; 5 | } 6 | 7 | fn main() { 8 | let output = unsafe { add(4, 4) }; 9 | assert!(output == 8, "4 + 4 != {}", output); 10 | } 11 | -------------------------------------------------------------------------------- /tests/static/xmake.lua: -------------------------------------------------------------------------------- 1 | target("foo") 2 | set_kind("static") 3 | add_files("src/foo.c") --------------------------------------------------------------------------------